From 1259e7e9a3a3010b671e9a6928219332f4a37d63 Mon Sep 17 00:00:00 2001 From: David Bourgault Date: Wed, 14 Feb 2018 12:17:28 -0500 Subject: [PATCH] Bug 17015: DiscreteCalendar UI, Back-End and necessary scripts --- Koha/DiscreteCalendar.pm | 1298 ++++++++++++++++++++ Koha/Schema/Result/DiscreteCalendar.pm | 111 ++ .../bug_17015_part1_create_discrete_calendar.sql | 14 + .../bug_17015_part2_fill_discrete_calendar.perl | 171 +++ .../prog/en/modules/tools/discrete_calendar.tt | 787 ++++++++++++ misc/cronjobs/add_days_discrete_calendar.pl | 129 ++ tools/discrete_calendar.pl | 154 +++ 7 files changed, 2664 insertions(+) create mode 100644 Koha/DiscreteCalendar.pm create mode 100644 Koha/Schema/Result/DiscreteCalendar.pm create mode 100644 installer/data/mysql/atomicupdate/bug_17015_part1_create_discrete_calendar.sql create mode 100755 installer/data/mysql/atomicupdate/bug_17015_part2_fill_discrete_calendar.perl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt create mode 100755 misc/cronjobs/add_days_discrete_calendar.pl create mode 100755 tools/discrete_calendar.pl diff --git a/Koha/DiscreteCalendar.pm b/Koha/DiscreteCalendar.pm new file mode 100644 index 0000000..8830396 --- /dev/null +++ b/Koha/DiscreteCalendar.pm @@ -0,0 +1,1298 @@ +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 strict; +use warnings; + +use CGI qw ( -utf8 ); +use Carp; +use DateTime; +use DateTime::Format::Strptime; + +use C4::Context; +use C4::Output; +use Koha::Database; +use Koha::DateUtils; + +# Global variables to make code more readable +our $HOLIDAYS = { + EXCEPTION => 'E', + REPEATABLE => 'R', + SINGLE => 'S', + NEED_VALIDATION => 'N', + FLOAT => 'F', + WEEKLY => 'W', + NONE => 'none' +}; + +=head1 NAME + +Koha::DiscreteCalendar - Object containing a branches calendar, working with the SQL database + +=head1 SYNOPSIS + + use Koha::DiscreteCalendar + + my $c = Koha::DiscreteCalendar->new( branchcode => 'MAIN' ); + my $dt = DateTime->now(); + + # are we open + $open = $c->is_holiday($dt); + # when will item be due if loan period = $dur (a DateTime::Duration object) + $duedate = $c->addDate($dt,$dur,'days'); + + +=head1 DESCRIPTION + + Implements 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'; + } + $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} = ''; + $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 methode 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) = @_; + + $copyBranch = '' unless $copyBranch; + my $schema = Koha::Database->new->schema; + + my $branch_rs = $schema->resultset('DiscreteCalendar')->search({ + branchcode => $copyBranch + }); + + while(my $row = $branch_rs->next()){ + $schema->resultset('DiscreteCalendar')->create({ + date => $row->date(), + branchcode => $newBranch, + is_opened => $row->is_opened(), + holiday_type => $row->holiday_type(), + open_hour => $row->open_hour(), + close_hour => $row->close_hour(), + }); + } + +} + +=head2 get_date_info + + my $date = $calendar->get_date_info; + +Returns a reference-to-hash representing a DiscreteCalendar date data object. +The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, +C<$open_hour>, C<$close_hour> and C<$note>. + +=cut + +sub get_date_info { + my ($self, $date) = @_; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + #String dates for Database usage + my $date_string = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + select => [ 'date', { DATE => 'date' } ], + as => [qw/ date date /], + where => \['DATE(?) = date', $date_string ], + columns =>[ qw/ branchcode holiday_type open_hour close_hour note/] + }, + ); + my $dateDTO; + while (my $date = $rs->next()){ + $dateDTO = { + date => $date->date(), + branchcode => $date->branchcode(), + holiday_type => $date->holiday_type() , + open_hour => $date->open_hour(), + close_hour => $date->close_hour(), + note => $date->note() + }; + } + + return $dateDTO; +} + +=head2 get_max_date + + my $maxDate = $calendar->get_max_date(); + +Returns the furthest date available in the databse of current branch. + +=cut + +sub get_max_date { + my $self = shift; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + select => [{ MAX => 'date' } ], + as => [qw/ max /], + } + ); + + return $rs->next()->get_column('max'); +} + +=head2 get_min_date + + my $minDate = $calendar->get_min_date(); + +Returns the oldest date available in the databse of current branch. + +=cut + +sub get_min_date { + my $self = shift; + my $branchcode = $self->{branchcode}; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode + }, + { + select => [{ MIN => 'date' } ], + as => [qw/ min /], + } + ); + + return $rs->next()->get_column('min'); +} + +=head2 get_unique_holidays + + my @unique_holidays = $calendar->get_unique_holidays(); + +Returns an array of all the date objects that are unique holidays. + +=cut + +sub get_unique_holidays { + my $self = shift; + my $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/], + } + ); + 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 $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/], + } + ); + 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 $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/], + } + ); + 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 $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/], + } + ); + + 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 $branchcode = $self->{branchcode}; + my @week_days; + my $schema = Koha::Database->new->schema; + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + holiday_type => $HOLIDAYS->{WEEKLY}, + branchcode => $branchcode, + }, + { + select => [{ DAYOFWEEK => 'date'}, 'note'], + as => [qw/ weekday note /], + distinct => 1, + } + ); + + 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} || DateTime->today; + + my $branchcode = $self->{branchcode}; + + my $schema = Koha::Database->new->schema; + $schema->{AutoCommit} = 0; + $schema->storage->txn_begin; + my $dtf = $schema->storage->datetime_parser; + + #String dates for Database usage + my $start_date_string = $dtf->format_datetime($start_date); + my $end_date_string = $dtf->format_datetime($end_date); + $today = $dtf->format_datetime($today); + + 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 >= ? 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], '>=' => $today}}; + 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], '>=' => $today}]}; + } + 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 $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date]]}, + } + ); + while (my $date = $rs->next()){ + $date->update(\%updateValues); + } + + }else { + #Update date(s)/Remove holidays + my $where = { date => { -between => [$start_date_string, $end_date_string], '>=' => $today}}; + 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], '>=' => $today}]}; + } + 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}; + } + + 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; +} + +=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 = 0 if $rs->first()->is_opened(); + $isHoliday = 1 if !$rs->first()->is_opened(); + } + + 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 + my $int_dt = $end_date; + $end_date = $start_date; + $start_date = $int_dt; + } + + my $schema = Koha::Database->new->schema; + my $dtf = $schema->storage->datetime_parser; + $start_date = $dtf->format_datetime($start_date); + $end_date = $dtf->format_datetime($end_date); + + 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; + $date = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + where => \['date > date(?)', $date], + order_by => { -asc => 'date' }, + rows => 1 + } + ); + return dt_from_string( $rs->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; + $date = $dtf->format_datetime($date); + + my $rs = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + is_opened => 1, + }, + { + where => \['date < date(?)', $date], + order_by => { -desc => 'date' }, + rows => 1 + } + ); + return dt_from_string( $rs->next()->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 = $schema->storage->datetime_parser; + $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, + }, + { + where => \['date = DATE(?)', $start_date], + } + ); + + my $return_day = $schema->resultset('DiscreteCalendar')->search( + { + branchcode => $branchcode, + }, + { + where => \['date = DATE(?)', $end_date], + } + ); + + #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 addDate + + my $dt = $calendar->addDate($date, $dur, $unit) + +C<$date> is a DateTime object representing the starting date of the interval. +C<$offset> is a 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 addDate { + 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; \ No newline at end of file diff --git a/Koha/Schema/Result/DiscreteCalendar.pm b/Koha/Schema/Result/DiscreteCalendar.pm new file mode 100644 index 0000000..6db32c8 --- /dev/null +++ b/Koha/Schema/Result/DiscreteCalendar.pm @@ -0,0 +1,111 @@ +use utf8; +package Koha::Schema::Result::DiscreteCalendar; + +# Created by DBIx::Class::Schema::Loader +# DO NOT MODIFY THE FIRST PART OF THIS FILE + +=head1 NAME + +Koha::Schema::Result::DiscreteCalendar + +=cut + +use strict; +use warnings; + +use base 'DBIx::Class::Core'; + +=head1 TABLE: C + +=cut + +__PACKAGE__->table("discrete_calendar"); + +=head1 ACCESSORS + +=head2 date + + data_type: 'datetime' + datetime_undef_if_invalid: 1 + is_nullable: 0 + +=head2 branchcode + + data_type: 'varchar' + is_nullable: 0 + size: 10 + +=head2 is_opened + + data_type: 'tinyint' + default_value: 1 + is_nullable: 1 + +=head2 holiday_type + + data_type: 'varchar' + default_value: (empty string) + is_nullable: 1 + size: 1 + +=head2 note + + data_type: 'varchar' + default_value: (empty string) + is_nullable: 1 + size: 30 + +=head2 open_hour + + data_type: 'time' + is_nullable: 0 + +=head2 close_hour + + data_type: 'time' + is_nullable: 0 + +=cut + +__PACKAGE__->add_columns( + "date", + { + data_type => "datetime", + datetime_undef_if_invalid => 1, + is_nullable => 0, + }, + "branchcode", + { data_type => "varchar", is_nullable => 0, size => 10 }, + "is_opened", + { data_type => "tinyint", default_value => 1, is_nullable => 1 }, + "holiday_type", + { data_type => "varchar", default_value => "", is_nullable => 1, size => 1 }, + "note", + { data_type => "varchar", default_value => "", is_nullable => 1, size => 30 }, + "open_hour", + { data_type => "time", is_nullable => 0 }, + "close_hour", + { data_type => "time", is_nullable => 0 }, +); + +=head1 PRIMARY KEY + +=over 4 + +=item * L + +=item * L + +=back + +=cut + +__PACKAGE__->set_primary_key("branchcode", "date"); + + +# Created by DBIx::Class::Schema::Loader v0.07045 @ 2017-04-19 10:07:41 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wtctW8ZzCkyCZFZmmavFEw + + +# You can replace this text with custom code or comments, and it will be preserved on regeneration +1; diff --git a/installer/data/mysql/atomicupdate/bug_17015_part1_create_discrete_calendar.sql b/installer/data/mysql/atomicupdate/bug_17015_part1_create_discrete_calendar.sql new file mode 100644 index 0000000..cd299c6 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_17015_part1_create_discrete_calendar.sql @@ -0,0 +1,14 @@ +-- Bugzilla 17015 +-- New koha calendar +-- Create discrete_calendar table to keep track of library day's information + +CREATE TABLE `discrete_calendar` ( + `date` datetime NOT NULL, + `branchcode` varchar(10) NOT NULL, + `is_opened` tinyint(1) DEFAULT 1, + `holiday_type` varchar(1) DEFAULT '', + `note` varchar(30) DEFAULT '', + `open_hour` time NOT NULL, + `close_hour` time NOT NULL, + PRIMARY KEY (`branchcode`,`date`) +); diff --git a/installer/data/mysql/atomicupdate/bug_17015_part2_fill_discrete_calendar.perl b/installer/data/mysql/atomicupdate/bug_17015_part2_fill_discrete_calendar.perl new file mode 100755 index 0000000..c7f823c --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_17015_part2_fill_discrete_calendar.perl @@ -0,0 +1,171 @@ +#!/usr/bin/perl + +# +# Script that fills the discrete_calendar table with dates, using the other date-related tables +# +use strict; +use warnings; +use DateTime; +use DateTime::Format::Strptime; +use Data::Dumper; +use Getopt::Long; +use C4::Context; + +# Options +my $help = 0; +my $daysInFuture = 365; +GetOptions ( + 'days|?|d=i' => \$daysInFuture, + 'help|?|h' => \$help); +my $usage = << 'ENDUSAGE'; + +Script that manages the discrete_calendar table. + +This script has the following parameters : + --days --d : how many days in the future will be created, by default it's 365 + -h --help: this message + +ENDUSAGE + +if ($help) { + print $usage; + exit; +} +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +my $currentDate = DateTime->today; + +# two years ago +my $startDate = DateTime->new( +day => $currentDate->day(), +month => $currentDate->month(), +year => $currentDate->year()-2, +time_zone => C4::Context->tz() +)->truncate( to => 'day' ); + +# a year into the future +my $endDate = DateTime->new( +day => $currentDate->day(), +month => $currentDate->month(), +year => $currentDate->year(), +time_zone => C4::Context->tz() +)->truncate( to => 'day' ); +$endDate->add(days => $daysInFuture); + +#Added a default (standard) branch. +my $add_default_branch = 'INSERT IGNORE INTO branches (branchname, branchcode) VALUES(?,?)'; +my $add_Branch_Sth = $dbh->prepare($add_default_branch); +$add_Branch_Sth->execute('Default', ''); +# finds branches; +my $selectBranchesSt = 'SELECT branchcode FROM branches'; +my $selectBranchesSth = $dbh->prepare($selectBranchesSt); +$selectBranchesSth->execute(); +my @branches = (); +for my $branchCode ( $selectBranchesSth->fetchrow_array ) { + print $branchCode . "\n"; + push @branches,$branchCode; +} +print "REACH\n"; +print Dumper(\@branches); + + +# finds what days are closed for each branch +my %repeatableHolidaysPerBranch = (); +my %specialHolidaysPerBranch = (); +my $selectWeeklySt; +my $selectWeeklySth; + +foreach my $branch (@branches){ + + $selectWeeklySt = 'SELECT weekday, title, day, month FROM repeatable_holidays WHERE branchcode = ?'; + $selectWeeklySth = $dbh->prepare($selectWeeklySt); + $selectWeeklySth->execute($branch); + + my @weeklyHolidays = (); + + while ( my ($weekDay, $title, $day, $month) = $selectWeeklySth->fetchrow_array ) { + push @weeklyHolidays,{weekday => $weekDay, title => $title, day => $day, month => $month}; + + } + + $repeatableHolidaysPerBranch{$branch} = \@weeklyHolidays; + + my $selectSpecialHolidayDateSt = 'SELECT day,month,year,title FROM special_holidays WHERE branchcode = ? AND isexception = 0'; + my $specialHolidayDatesSth = $dbh->prepare($selectSpecialHolidayDateSt); + $specialHolidayDatesSth -> execute($branch); + # Tranforms dates from specialHolidays table in DateTime for our new table + my @specialHolidayDates = (); + while ( my ($day, $month, $year, $title) = $specialHolidayDatesSth->fetchrow_array ) { + + my $specialHolidayDate = DateTime->new( + day => $day, + month => $month, + year => $year, + time_zone => C4::Context->tz() + )->truncate( to => 'day' ); + push @specialHolidayDates,{date=>$specialHolidayDate, title=> $title}; + } + + $specialHolidaysPerBranch{$branch} = \@specialHolidayDates; +} +# Fills table with dates and sets 'is_opened' according to the branch's weekly restrictions (repeatable_holidays) +my $insertDateSt; +my $insertDateSth; + +# Loop that does everything in the world +for (my $tempDate = $startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){ + foreach my $branch (@branches){ + my $dayOfWeek = $tempDate->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; + + my $open_hour = "09:00:00"; + my $close_hour = "17:00:00"; + + # Finds closed days + my $is_opened =1; + my $specialDescription = ""; + my $holiday_type =''; + $dayOfWeek = $tempDate->day_of_week; + foreach my $holidayWeekDay (@{$repeatableHolidaysPerBranch{$branch}}){ + if($holidayWeekDay->{weekday} && $dayOfWeek == $holidayWeekDay->{weekday}){ + $is_opened = 0; + $specialDescription = $holidayWeekDay->{title}; + $holiday_type = 'W'; + }elsif($holidayWeekDay->{day} && $holidayWeekDay->{month}){ + my $date = DateTime->new( + day => $holidayWeekDay->{day}, + month => $holidayWeekDay->{month}, + year => $tempDate->year(), + time_zone => C4::Context->tz() + )->truncate( to => 'day' ); + + if ($tempDate == $date) { + $is_opened = 0; + $specialDescription = $holidayWeekDay->{title}; + $holiday_type = 'R'; + } + } + } + + foreach my $specialDate (@{$specialHolidaysPerBranch{$branch}}){ + if($tempDate->datetime() eq $specialDate->{date}->datetime() ){ + $is_opened = 0; + $specialDescription = $specialDate->{title}; + $holiday_type = 'E'; + } + } + #final insert statement + + $insertDateSt = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,holiday_type,note,open_hour,close_hour) VALUES (?,?,?,?,?,?,?)'; + $insertDateSth = $dbh->prepare($insertDateSt); + $insertDateSth->execute($tempDate,$branch,$is_opened,$holiday_type,$specialDescription,$open_hour,$close_hour); + } +} +# If everything went well we commit to the database +$dbh->commit(); 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 0000000..5e7461c --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt @@ -0,0 +1,787 @@ +[% USE Branches %] +[% INCLUDE 'doc-head-open.inc' %] +Koha › Tools › [% Branches.GetName( branch ) %] calendar +[% INCLUDE 'doc-head-close.inc' %] +[% INCLUDE 'calendar.inc' %] + + +[% INCLUDE 'datatables.inc' %] + + + + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + + +
+ +
+
+
+

[% Branches.GetName( branch ) %] calendar

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

Calendar information

+
+ + [% UNLESS datesInfos %] +
+ Error! You have to run generate_discrete_calendar.pl in order to use Discrete Calendar. +
+ [% END %] + + [% IF no_branch_selected %] +
+ No library set! You are using the default calendar. +
+ [% END %] + +
+
+
+

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. + +
  14. +
  15. + + +
  16. +
  17. + + + + + + + + + + +
  18. +
+
+ + 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: +
      +
    • You can't edit passed dates
    • +
    • 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 %][% need_validation_holiday.note %]
+ [% END %] + + [% IF ( WEEKLY_HOLIDAYS ) %] +

Weekly - Repeatable holidays

+ + + + + + + + + [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %] + + + + + + [% END %] + +
Day of weekTitle
[% WEEK_DAYS_LOO.weekday %][% WEEK_DAYS_LOO.note %]
+[% 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 %]/[% DAY_MONTH_HOLIDAYS_LOO.month %][% DAY_MONTH_HOLIDAYS_LOO.month %]/[% DAY_MONTH_HOLIDAYS_LOO.day %][% DAY_MONTH_HOLIDAYS_LOO.note %]
+[% END %] + +[% IF ( UNIQUE_HOLIDAYS ) %] +

Unique holidays

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

Floating holidays

+ + + + + + + + + [% FOREACH float_holiday IN FLOAT_HOLIDAYS %] + + + + + [% END %] + +
DateTitle
[% float_holiday.outputdate %][% float_holiday.note %]
+[% END %] +
+
+
+
+
+ +
+[% INCLUDE 'tools-menu.inc' %] +
+
+[% INCLUDE 'intranet-bottom.inc' %] diff --git a/misc/cronjobs/add_days_discrete_calendar.pl b/misc/cronjobs/add_days_discrete_calendar.pl new file mode 100755 index 0000000..83fd836 --- /dev/null +++ b/misc/cronjobs/add_days_discrete_calendar.pl @@ -0,0 +1,129 @@ +#!/usr/bin/perl + +# +# This script adds one day into discrete_calendar table based on the same day from the week before +# +use strict; +use warnings; +use DateTime; +use DateTime::Format::Strptime; +use Data::Dumper; +use Getopt::Long; +use C4::Context; +use Koha::Database; +use Koha::DiscreteCalendar; + +# Options +my $help = 0; +my $daysInFuture = 1; +my $debug = 0; +GetOptions ( + 'help|?|h' => \$help, + 'n=i' => \$daysInFuture, + '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 + -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 +$query = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode'; +$statement = $dbh->prepare($query); +$statement->execute(); +my @branches = (); +for my $branchcode ($statement->fetchrow_array ) { + push @branches,$branchcode; +} + +#get the latest date in the table +$query = "SELECT MAX(date) FROM discrete_calendar"; +$statement = $dbh->prepare($query); +$statement->execute(); +my $latestedDate = $$statement->fetchrow_array; +my $parser = DateTime::Format::Strptime->new( + pattern => '%Y-%m-%d %H:%M:%S', + on_error => 'croak', +); + +$latestedDate = $parser->parse_datetime($latestedDate); + +my $newDay = $latestedDate->clone(); +$latestedDate->add(days => $daysInFuture); + +for ($newDay->add(days => 1); $newDay <= $latestedDate; $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 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 (?,?,?,?,?)'; + my $note =''; + #insert into discrete_calendar for each branch + foreach my $branchCode(@branches){ + $$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 ) = $$statement->fetchrow_array; + my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,holiday_type, note,open_hour,close_hour) VALUES (?,?,?,?,?,?,?)'; + $$statement = $dbh->prepare($add_Day); + $$statement->execute($newDay,$branchCode,$is_opened,$holiday_type,$note, $open_hour,$close_hour); + + if($debug && !$@){ + warn "Added day $newDay to $branchCode is opened : $is_opened, holiday_type : $holiday_type, note: $note, open_hour : $open_hour, close_hour : $close_hour \n"; + }elsif($@){ + warn "Failed to add day $newDay to $branchCode : $_\n"; + } + } +} +# If everything went well we commit to the database +$schema->storage->txn_commit; \ No newline at end of file diff --git a/tools/discrete_calendar.pl b/tools/discrete_calendar.pl new file mode 100755 index 0000000..2168e42 --- /dev/null +++ b/tools/discrete_calendar.pl @@ -0,0 +1,154 @@ +#!/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 strict; +use warnings; + +use CGI qw ( -utf8 ); + +use C4::Auth; +use C4::Output; + +use Koha::DateUtils; +use Koha::DiscreteCalendar; + +my $input = new CGI; + +# Get the template to use +my ($template, $loggedinuser, $cookie) + = get_template_and_user({template_name => "tools/discrete_calendar.tt", + type => "intranet", + query => $input, + authnotrequired => 0, + 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_copyFrom') ||''; + my $from_endDate = $input->param('toDate') || ''; + my $to_startDate = $input->param('to_copyFrom') || ''; + my $to_endDate = $input->param('to_copyTo') || ''; + my $daysnumber= $input->param('daysnumber'); + + $from_startDate = dt_from_string(scalar $from_startDate) if$from_startDate ne ''; + $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne ''; + $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne ''; + $to_endDate = dt_from_string(scalar $to_endDate) if $to_endDate ne ''; + + $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber); +} elsif($action eq 'edit'){ + my $openHour = $input->param('openHour'); + my $closeHour = $input->param('closeHour'); + my $endDate = $input->param('toDate'); + my $deleteType = $input->param('deleteType') || 0; + #Get today from javascript for a precise local time + my $local_today = dt_from_string( $input->param('local_today'), 'iso'); + + my $startDate = dt_from_string(scalar $input->param('from_copyFrom')); + + if($endDate ne '' ) { + $endDate = 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 + }); +} + +# 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($branch); + +#Calendar minimum date +my $maxDate = $calendar->get_max_date($branch); + +my @datesInfos = $calendar->get_dates_info($branch); + +$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; -- 2.7.4