Bugzilla – Attachment 71634 Details for
Bug 17015
New Koha Calendar
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 17015 - DiscreteCalendar UI, Back-End and necessary scripts
Bug-17015---DiscreteCalendar-UI-Back-End-and-neces.patch (text/plain), 95.27 KB, created by
David Bourgault
on 2018-02-14 19:17:16 UTC
(
hide
)
Description:
Bug 17015 - DiscreteCalendar UI, Back-End and necessary scripts
Filename:
MIME Type:
Creator:
David Bourgault
Created:
2018-02-14 19:17:16 UTC
Size:
95.27 KB
patch
obsolete
>From d20e867db18cd6ac222725f548297a7b1d36f3a8 Mon Sep 17 00:00:00 2001 >From: David Bourgault <david.bourgault@inlibro.com> >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 <http://www.gnu.org/licenses>. >+ >+#####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<open_hours_between>/ >+ >+=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<discrete_calendar> >+ >+=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</branchcode> >+ >+=item * L</date> >+ >+=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' %] >+<title>Koha › Tools › [% Branches.GetName( branch ) %] calendar</title> >+[% INCLUDE 'doc-head-close.inc' %] >+[% INCLUDE 'calendar.inc' %] >+<link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css" /> >+<script type="text/javascript" src="[% interface %]/lib/jquery/plugins/jquery-ui-timepicker-addon.min.js"></script> >+[% INCLUDE 'datatables.inc' %] >+ <script type="text/javascript"> >+ //<![CDATA[ >+ var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays")); >+ // Array containing all the information about each date in the calendar. >+ var datesInfos = new Array(); >+ [% FOREACH date IN datesInfos %] >+ datesInfos["[% date.date %]"] = { >+ title : "[% date.note %]", >+ outputdate : "[% date.outputdate %]", >+ holiday_type:"[% date.holiday_type %]", >+ open_hour: "[% date.open_hour %]", >+ close_hour: "[% date.close_hour %]" >+ }; >+ [% END %] >+ >+ /** >+ * Displays the details of the selected date on a side panel >+ */ >+ function showHoliday (date_obj, dateString, dayName, day, month, year, weekDay, title, holidayType) { >+ $("#newHoliday").slideDown("fast"); >+ $("#copyHoliday").slideUp("fast"); >+ $('#newDaynameOutput').html(dayName); >+ $('#newDayname').val(dayName); >+ $('#newBranchNameOutput').html($("#branch :selected").text()); >+ $(".newHoliday ,#branch").val($('#branch').val()); >+ $('#newDayOutput').html(day); >+ $(".newHoliday #Day").val(day); >+ $(".newHoliday #Month").val(month); >+ $(".newHoliday #Year").val(year); >+ $("#newMonthOutput").html(month); >+ $("#newYearOutput").html(year); >+ $(".newHoliday, #Weekday").val(weekDay); >+ >+ $('.newHoliday #title').val(title); >+ $('#HolidayType').val(holidayType); >+ $('#days_of_week option[value="'+ (weekDay + 1) +'"]').attr('selected', true); >+ $('#openHour').val(datesInfos[dateString].open_hour); >+ $('#closeHour').val(datesInfos[dateString].close_hour); >+ $('#local_today').val(getSeparetedDate(new Date()).dateString); >+ >+ //This changes the label of the date type on the edit panel >+ if(holidayType == 'W') { >+ $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly")); >+ } else if(holidayType == 'R') { >+ $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly")); >+ } else if(holidayType == 'F') { >+ $("#holtype").attr("class","key float").html(_("Floating holiday")); >+ } else if(holidayType == 'N') { >+ $("#holtype").attr("class","key exception").html(_("Needs validation")); >+ } else if(holidayType == 'E') { >+ $("#holtype").attr("class","key holiday").html(_("Unique holiday")); >+ } else{ >+ $("#holtype").attr("class","key normalday").html(_("Working day ")); >+ } >+ >+ //Select the correct holiday type on the dropdown menu >+ if (datesInfos[dateString].holiday_type !=''){ >+ var type = datesInfos[dateString].holiday_type; >+ $('#holidayType option[value="'+ type +'"]').attr('selected', true) >+ }else{ >+ $('#holidayType option[value="none"]').attr('selected', true) >+ } >+ >+ //If it is a weekly or repeatable holiday show the option to delete the type >+ if(datesInfos[dateString].holiday_type == 'W' || datesInfos[dateString].holiday_type == 'R'){ >+ $('#deleteType').show("fast"); >+ }else{ >+ $('#deleteType').hide("fast"); >+ } >+ >+ //This value is to disable and hide input when the date is in the past, because you can't edit it. >+ var value = false; >+ var today = new Date(); >+ today.setHours(0,0,0,0); >+ if(date_obj < today ){ >+ $("#holtype").attr("class","key past-date").html(_("Past date")); >+ $("#CopyRadioButton").attr("checked", "checked"); >+ value = true; >+ $(".CopyDatePanel").toggle(value); >+ } >+ $("#title").prop('disabled', value); >+ $("#holidayType select").prop('disabled', value); >+ $("#openHour").prop('disabled', value); >+ $("#closeHour").prop('disabled', value); >+ $("#EditRadioButton").parent().toggle(!value); >+ >+ } >+ >+ function hidePanel(aPanelName) { >+ $("#"+aPanelName).slideUp("fast"); >+ } >+ >+ function changeBranch () { >+ var branch = $("#branch option:selected").val(); >+ location.href='/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate %]"; >+ } >+ >+ function Help() { >+ newin=window.open("/cgi-bin/koha/help.pl","KohaHelp",'width=600,height=600,toolbar=false,scrollbars=yes'); >+ } >+ >+ // This function gives css clases to each kind of day >+ function dateStatusHandler(date) { >+ date = getSeparetedDate(date); >+ var day = date.day; >+ var month = date.month; >+ var year = date.year; >+ var weekDay = date.weekDay; >+ var dayName = weekdays[weekDay]; >+ var dateString = date.dateString; >+ var today = new Date(); >+ today.setHours(0,0,0,0); >+ >+ if (datesInfos[dateString] && datesInfos[dateString].holiday_type =='W'){ >+ return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)]; >+ } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'R') { >+ return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)]; >+ } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'N') { >+ return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)]; >+ } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'F') { >+ return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)]; >+ } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'E') { >+ return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)]; >+ } else { >+ if(date.date_obj < today ){ >+ return [true, "past-date", _("Past day")]; >+ }else{ >+ return [true, "normalday", _("Normal day")]; >+ } >+ } >+ } >+ >+ /* This function is in charge of showing the correct panel considering the kind of holiday */ >+ function dateChanged(text, date) { >+ date = getSeparetedDate(date); >+ var day = date.day; >+ var month = date.month; >+ var year = date.year; >+ var weekDay = date.weekDay; >+ var dayName = weekdays[weekDay]; >+ var dateString = date.dateString; >+ var date_obj = date.date_obj; >+ //set value of form hidden field >+ $('#from_copyFrom').val(text); >+ >+ showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type); >+ } >+ >+ /** >+ * This function separate a given date object a returns an array containing all needed information about the date. >+ */ >+ function getSeparetedDate(date){ >+ var mydate = new Array(); >+ var day = (date.getDate() < 10 ? '0' : '') + date.getDate(); >+ var month = ((date.getMonth()+1) < 10 ? '0' : '') + (date.getMonth() +1); >+ var year = date.getFullYear(); >+ var weekDay = date.getDay(); >+ //iso date string >+ var dateString = year + '-' + month + '-' + day; >+ mydate = { >+ date_obj : date, >+ dateString : dateString, >+ weekDay: weekDay, >+ year: year, >+ month: month, >+ day: day >+ }; >+ >+ return mydate; >+ } >+ >+ /** >+ * Valide the forms before send them to the backend >+ */ >+ function validateForm(form){ >+ if(form =='newHoliday' && $('#CopyRadioButton').is(':checked')){ >+ if($('#to_copyFromDatePicker').val() =='' || $('#to_copyToDatePicker').val() ==''){ >+ alert("You have to pick a FROM and TO in the Copy to different dates."); >+ return false; >+ }else if ($('#from_copyToDatePicker').val()){ >+ var from_DateFrom = new Date($("#jcalendar-container").datepicker("getDate")); >+ var from_DateTo = new Date($('#from_copyToDatePicker').datepicker("getDate")); >+ var to_DateFrom = new Date($('#to_copyFromDatePicker').datepicker("getDate")); >+ var to_DateTo = new Date($('#to_copyToDatePicker').datepicker("getDate")); >+ >+ var from_start = Math.round( from_DateFrom.getTime() / (3600*24*1000)); //days as integer from.. >+ var from_end = Math.round( from_DateTo.getTime() / (3600*24*1000)); >+ var to_start = Math.round( to_DateFrom.getTime() / (3600*24*1000)); >+ var to_end = Math.round( to_DateTo.getTime() / (3600*24*1000)); >+ >+ var from_daysDiff = from_end - from_start +1; >+ var to_daysDiff = to_end - to_start + 1; >+ if(from_daysDiff == to_daysDiff){ >+ $('#daysnumber').val(to_daysDiff); >+ return true; >+ }else{ >+ alert("You have to pick the same number of days if you choose 2 ranges"); >+ return false; >+ } >+ } >+ }else if(form == 'CopyCalendar'){ >+ if ($('#newBranch').val() ==''){ >+ alert("Please select a copy to calendar."); >+ return false; >+ }else{ >+ return true; >+ } >+ }else { >+ return true; >+ } >+ } >+ >+ function go_to_date(isoDate){ >+ //I added the time to get around the timezone >+ var date = getSeparetedDate(new Date(isoDate + " 00:00:00")); >+ var day = date.day; >+ var month = date.month; >+ var year = date.year; >+ var weekDay = date.weekDay; >+ var dayName = weekdays[weekDay]; >+ var dateString = date.dateString; >+ var date_obj = date.date_obj; >+ >+ $("#jcalendar-container").datepicker("setDate", date_obj); >+ showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type); >+ } >+ >+ /** >+ *Check if date range have the same opening, closing hours and holiday type if there's one. >+ */ >+ function checkRange(date){ >+ date = new Date(date); >+ $('#toDate').val(getSeparetedDate(date).dateString); >+ var fromDate = new Date($("#jcalendar-container").datepicker("getDate")); >+ var sameHoliday =true; >+ var sameOpenHours =true; >+ var sameCloseHours =true; >+ >+ $('#days_of_week option[value="everyday"]').attr('selected', true); >+ for (var i = fromDate; i <= date ; i.setDate(i.getDate() + 1)) { >+ var myDate1 = getSeparetedDate(i); >+ var date1 = myDate1.dateString; >+ var holidayType1 = datesInfos[date1].holiday_type; >+ var open_hours1 = datesInfos[date1].open_hour; >+ var close_hours1 = datesInfos[date1].close_hour; >+ for (var j = fromDate; j <= date ; j.setDate(j.getDate() + 1)) { >+ var myDate2 = getSeparetedDate(j); >+ var date2 = myDate2.dateString; >+ var holidayType2 = datesInfos[date2].holiday_type; >+ var open_hours2 = datesInfos[date2].open_hour; >+ var close_hours2 = datesInfos[date2].close_hour; >+ >+ if (sameHoliday && holidayType1 != holidayType2){ >+ $('#holidayType option[value="empty"]').attr('selected', true); >+ sameHoliday=false; >+ } >+ if(sameOpenHours && (open_hours1 != open_hours2)){ >+ $('#openHour').val(''); >+ sameOpenHours=false; >+ } >+ if(sameCloseHours && (close_hours1 != close_hours2)){ >+ $('#closeHour').val(''); >+ sameCloseHours=false; >+ } >+ } >+ if (!sameOpenHours && !sameCloseHours && !sameHoliday){ >+ return false; >+ } >+ } >+ return true; >+ } >+ >+ $(document).ready(function() { >+ $(".hint").hide(); >+ $("#branch").change(function(){ >+ changeBranch(); >+ }); >+ $("#holidayweeklyrepeatable>tbody>tr").each(function(){ >+ var first_td = $(this).find('td').first(); >+ first_td.html(weekdays[first_td.html()]); >+ }); >+ $("a.helptext").click(function(){ >+ $(this).parent().find(".hint").toggle(); return false; >+ }); >+ //Set the correct coloring, default date and the date ranges for all datepickers >+ $.datepicker.setDefaults({ >+ beforeShowDay: function(thedate) { >+ return dateStatusHandler(thedate); >+ }, >+ defaultDate: new Date("[% keydate %]"), >+ minDate: new Date("[% minDate %]"), >+ maxDate: new Date("[% maxDate %]") >+ }); >+ //Main datepicker >+ $("#jcalendar-container").datepicker({ >+ onSelect: function(dateText, inst) { >+ dateChanged(dateText, $(this).datepicker("getDate")); >+ }, >+ }); >+ $('#from_copyToDatePicker').datepicker(); >+ $("#from_copyToDatePicker").change(function(){ >+ checkRange($(this).datepicker("getDate")); >+ $('#from_copyTo').val(($(this).val())); >+ if($('#from_copyToDatePicker').val()){ >+ $('#days_of_week').show("fast"); >+ }else{ >+ $('#days_of_week').hide("fast"); >+ } >+ }); >+ //Datepickers for copy dates feature >+ $('#to_copyFromDatePicker').datepicker(); >+ $("#to_copyFromDatePicker").change(function(){ >+ $('#to_copyFrom').val(($(this).val())); >+ }); >+ $('#to_copyToDatePicker').datepicker(); >+ $("#to_copyToDatePicker").change(function(){ >+ $('#to_copyTo').val(($(this).val())); >+ }); >+ //Timepickers for open and close hours >+ $('#openHour').timepicker({ >+ showOn : 'focus', >+ timeFormat: 'HH:mm:ss', >+ showSecond: false, >+ stepMinute: 5, >+ }); >+ $('#closeHour').timepicker({ >+ showOn : 'focus', >+ timeFormat: 'HH:mm:ss', >+ showSecond: false, >+ stepMinute: 5, >+ }); >+ >+ $('.newHoliday input[type="radio"]').click(function () { >+ if ($(this).attr("id") == "CopyRadioButton") { >+ $(".CopyToBranchPanel").hide('fast'); >+ $(".CopyDatePanel").show('fast'); >+ } else if ($(this).attr("id") == "CopyToBranchRadioButton"){ >+ $(".CopyDatePanel").hide('fast'); >+ $(".CopyToBranchPanel").show('fast'); >+ } else{ >+ $(".CopyDatePanel").hide('fast'); >+ $(".CopyToBranchPanel").hide('fast'); >+ } >+ }); >+ >+ $(".hidePanel").on("click",function(){ >+ if( $(this).hasClass("showHoliday") ){ >+ hidePanel("showHoliday"); >+ }if ($(this).hasClass('newHoliday')) { >+ hidePanel("newHoliday"); >+ }else { >+ hidePanel("copyHoliday"); >+ } >+ }); >+ >+ $("#deleteType_checkbox").on("change", function(){ >+ if($("#deleteType_checkbox").is(':checked')){ >+ $('#holidayType option[value="none"]').attr('selected', true); >+ } >+ }); >+ $("#holidayType select").on("change", function(){ >+ if($("#holidayType select").val() == "R"){ >+ $('#days_of_week').hide("fast"); >+ }else if ($('#from_copyToDatePicker').val()){ >+ $('#days_of_week').show("fast"); >+ } >+ }); >+ }); >+ //]]> >+ </script> >+ <!-- Datepicker colors --> >+ <style type="text/css"> >+ #jcalendar-container .ui-datepicker { >+ 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 : Black; >+ 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 : Black; border : 1px solid #BCBCBC; >+ } >+ .past-date { >+ background-color : #e6e6e6; color : #555555; border : 1px solid #BCBCBC; >+ } >+ td.past-date a.ui-state-default { >+ background : #e6e6e6 ; color : #555555; >+ } >+ .float { >+ background-color : #66ff33; color : Black; border : 1px solid #BCBCBC; >+ } >+ .holiday { >+ background-color : #ffaeae; color : Black; border : 1px solid #BCBCBC; >+ } >+ .repeatableweekly { >+ background-color : #FFFF99; color : Black; border : 1px solid #BCBCBC; >+ } >+ .repeatableyearly { >+ background-color : #FFCC66; color : Black; border : 1px solid #BCBCBC; >+ } >+ td.exception a.ui-state-default { >+ background: #b3d4ff none; color : Black; border : 1px solid #BCBCBC; >+ } >+ td.float a.ui-state-default { >+ background: #66ff33 none; color : Black; border : 1px solid #BCBCBC; >+ } >+ td.holiday a.ui-state-default { >+ background: #ffaeae none; color : Black; border : 1px solid #BCBCBC; >+ } >+ td.repeatableweekly a.ui-state-default { >+ background: #FFFF99 none; color : Black; border : 1px solid #BCBCBC; >+ } >+ td.repeatableyearly a.ui-state-default { >+ background: #FFCC66 none; color : Black; border : 1px solid #BCBCBC; >+ } >+ .information { >+ z-index : ; background-color : #DCD2F1; width : 300px; display : none; border : 1px solid #000000; color : #000000; font-size : 8pt; font-weight : bold; background-color : #FFD700; cursor : pointer; padding : 2px; >+ } >+ .panel { >+ z-index : 1; display : none; border : 3px solid #CCC; padding : 3px; margin-top: .3em; background-color: #FEFEFE; >+ } >+ fieldset.brief { >+ border : 0; margin-top: 0; >+ } >+ h1 select { >+ width: 20em; >+ } >+ div.yui-b fieldset.brief ol { >+ font-size:100%; >+ } >+ div.yui-b fieldset.brief li, div.yui-b fieldset.brief li.radio { >+ padding:0.2em 0; >+ } >+ .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; >+ } >+ </style> >+</head> >+ >+<body id="tools_holidays" class="tools"> >+[% INCLUDE 'header.inc' %] >+[% INCLUDE 'cat-search.inc' %] >+ >+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> › <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> › [% Branches.GetName( branch ) %] calendar</div> >+ >+<div id="doc3" class="yui-t1"> >+ >+ <div id="bd"> >+ <div id="yui-main"> >+ <div class="yui-b"> >+ <h2>[% Branches.GetName( branch ) %] calendar</h2> >+ <div class="yui-g"> >+ <div class="yui-u first" style="width:60%"> >+ <label for="branch">Define the holidays for:</label> >+ <form method="post" onsubmit="return validateForm('CopyCalendar')"> >+ <select id="branch" name="branch"> >+ [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %] >+ </select> >+ Copy calendar to >+ <select id='newBranch' name ='newBranch'> >+ <option value=""></option> >+ [% FOREACH l IN Branches.all() %] >+ [% UNLESS branch == l.branchcode %] >+ <option value="[% l.branchcode %]">[% l.branchname %]</option> >+ [% END %] >+ [% END %] >+ </select> >+ <input type="hidden" name="action" value="copyBranch" /> >+ <input type="submit" value="Clone"> >+ </form> >+ <h3>Calendar information</h3> >+ <div id="jcalendar-container" style="float: left"></div> >+ <!-- ***************************** Panel to deal with new holidays ********************** --> >+ [% UNLESS datesInfos %] >+ <div class="alert alert-danger" style="float: left; margin-left:15px"> >+ <strong>Error!</strong> You have to run generate_discrete_calendar.pl in order to use Discrete Calendar. >+ </div> >+ [% END %] >+ >+ [% IF no_branch_selected %] >+ <div class="alert alert-danger" style="float: left; margin-left:15px"> >+ <strong>No library set!</strong> You are using the default calendar. >+ </div> >+ [% END %] >+ >+ <div class="panel newHoliday" id="newHoliday" style="float: left; margin-left:15px"> >+ <form method="post" onsubmit="return validateForm('newHoliday')"> >+ <fieldset class="brief"> >+ <h3>Edit date details</h3> >+ <span id="holtype"></span> >+ <ol> >+ <li> >+ <strong>Library:</strong> >+ <span id="newBranchNameOutput"></span> >+ <input type="hidden" id="branch" name="branch" /> >+ </li> >+ <li> >+ <strong>From date:</strong> >+ <span id="newDaynameOutput"></span>, >+ >+ [% IF ( dateformat == "us" ) %]<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "metric" ) %]<span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "dmydot" ) %]<span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>[% ELSE %]<span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>[% END %] >+ >+ <input type="hidden" id="newDayname" name="showDayname" /> >+ <input type="hidden" id="Day" name="Day" /> >+ <input type="hidden" id="Month" name="Month" /> >+ <input type="hidden" id="Year" name="Year" /> >+ </li> >+ <li class="dateinsert"> >+ <b>To date: </b> >+ <input type="text" id="from_copyToDatePicker" name="toDate" size="20" class="datepicker" /> >+ </li> >+ <li> >+ <label for="title">Title: </label><input type="text" name="Title" id="title" size="35" /> >+ </li> >+ <li id="holidayType"> >+ <label for="holidayType">Date type</label> >+ <select name ='holidayType'> >+ <option value="empty"></option> >+ <option value="none">Working day</option> >+ <option value="E">Unique holiday</option> >+ <option value="W">Weekly holiday</option> >+ <option value="R">Repeatable holiday</option> >+ <option value="F">Floating holiday</option> >+ <option value="N" disabled>Need validation</option> >+ </select> >+ </li> >+ <li id="days_of_week" style="display :none"> >+ <label for="day_of_week">Week day</label> >+ <select name ='day_of_week'> >+ <option value="everyday">Everyday</option> >+ <option value="1">Sundays</option> >+ <option value="2">Mondays</option> >+ <option value="3">Tuesdays</option> >+ <option value="4">Wednesdays</option> >+ <option value="5">Thursdays</option> >+ <option value="6">Fridays</option> >+ <option value="7">Saturdays</option> >+ </select> >+ </li> >+ <li class="radio" id="deleteType" style="display : none;" > >+ <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label> >+ <a href="#" class="helptext">[?]</a> >+ <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if working day is selected.</div> >+ </li> >+ <li> >+ <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' style="display :flex" > >+ </li> >+ <li> >+ <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' style="display :flex" > >+ </li> >+ <li class="radio"> >+ <input type="radio" name="action" id="EditRadioButton" value="edit" checked/> >+ <label for="EditRadioButton">Edit selected dates</label> >+ </li> >+ <li class="radio"> >+ <input type="radio" name="action" id="CopyRadioButton" value="copyDates" /> >+ <label for="CopyRadioButton">Copy to different dates</label> >+ <div class="CopyDatePanel" style="display:none; padding-left:15px"> >+ <b>From : </b> >+ <input type="text" id="to_copyFromDatePicker" size="20" class="datepicker"/> >+ <b>To : </b> >+ <input type="text" id="to_copyToDatePicker" size="20" class="datepicker"/> >+ </div> >+ <input type="hidden" name="daysnumber" id='daysnumber'> >+ <!-- These yyyy-mm-dd --> >+ <input type="hidden" name="from_copyFrom" id='from_copyFrom'> >+ <input type="hidden" name="from_copyTo" id='from_copyTo'> >+ <input type="hidden" name="to_copyFrom" id='to_copyFrom'> >+ <input type="hidden" name="to_copyTo" id='to_copyTo'> >+ <input type="hidden" name="local_today" id='local_today'> >+ </li> >+ </ol> >+ <fieldset class="action"> >+ <input type="submit" name="submit" value="Save" /> >+ <a href="#" class="cancel hidePanel newHoliday">Cancel</a> >+ </fieldset> >+ </fieldset> >+ </form> >+ </div> >+ >+<!-- ************************************************************************************** --> >+<!-- ****** MAIN SCREEN CODE ****** --> >+<!-- ************************************************************************************** --> >+ >+</div> >+<div class="yui-u" style="width : 40%"> >+ <div class="help"> >+ <h4>Hints</h4> >+ <ul> >+ <li>Search in the calendar the day you want to set as holiday.</li> >+ <li>Click the date to add or edit a holiday.</li> >+ <li>Specify how the holiday should repeat.</li> >+ <li>Click Save to finish.</li> >+ <li>PS: >+ <ul> >+ <li>You can't edit passed dates</li> >+ <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li> >+ </ul> >+ </li> >+ </ul> >+ <h4>Key</h4> >+ <p> >+ <span class="key normalday">Working day </span> >+ <span class="key holiday">Unique holiday</span> >+ <span class="key repeatableweekly">Holiday repeating weekly</span> >+ <span class="key repeatableyearly">Holiday repeating yearly</span> >+ <span class="key float">Floating holiday</span> >+ <span class="key exception">Need validation</span> >+ </p> >+ </div> >+<div id="holiday-list"> >+ >+ [% IF ( NEED_VALIDATION_HOLIDAYS ) %] >+ <h3>Need validation holidays</h3> >+ <table id="holidaysunique"> >+ <thead> >+ <tr> >+ <th class="exception">Date</th> >+ <th class="exception">Title</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %] >+ <tr> >+ <td><a href="#doc3" onclick="go_to_date('[% need_validation_holiday.date %]')"><span title="[% need_validation_holiday.DATE_SORT %]">[% need_validation_holiday.outputdate %]</span></a></td> >+ <td>[% need_validation_holiday.note %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+ </table> >+ [% END %] >+ >+ [% IF ( WEEKLY_HOLIDAYS ) %] >+ <h3>Weekly - Repeatable holidays</h3> >+ <table id="holidayweeklyrepeatable"> >+ <thead> >+ <tr> >+ <th class="repeatableweekly">Day of week</th> >+ <th class="repeatableweekly">Title</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %] >+ <tr> >+ <td>[% WEEK_DAYS_LOO.weekday %]</td> >+ </td> >+ <td>[% WEEK_DAYS_LOO.note %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+[% END %] >+ >+[% IF ( REPEATABLE_HOLIDAYS ) %] >+<h3>Yearly - Repeatable holidays</h3> >+<table id="holidaysyearlyrepeatable"> >+ <thead> >+ <tr> >+ [% IF ( dateformat == "metric" ) %] >+ <th class="repeatableyearly">Day/month</th> >+ [% ELSE %] >+ <th class="repeatableyearly">Month/day</th> >+ [% END %] >+ <th class="repeatableyearly">Title</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %] >+ <tr> >+ [% IF ( dateformat == "metric" ) %] >+ <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.day %]/[% DAY_MONTH_HOLIDAYS_LOO.month %]</span></td> >+ [% ELSE %] >+ <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.month %]/[% DAY_MONTH_HOLIDAYS_LOO.day %]</span></td> >+ [% END %] >+ <td>[% DAY_MONTH_HOLIDAYS_LOO.note %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+[% END %] >+ >+[% IF ( UNIQUE_HOLIDAYS ) %] >+<h3>Unique holidays</h3> >+<table id="holidaysunique"> >+ <thead> >+ <tr> >+ <th class="holiday">Date</th> >+ <th class="holiday">Title</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %] >+ <tr> >+ <td><a href="#doc3" onclick="go_to_date('[% HOLIDAYS_LOO.date %]')"><span title="[% HOLIDAYS_LOO.DATE_SORT %]">[% HOLIDAYS_LOO.outputdate %]</span></a></td> >+ <td>[% HOLIDAYS_LOO.note %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+[% END %] >+ >+[% IF ( FLOAT_HOLIDAYS ) %] >+<h3>Floating holidays</h3> >+<table id="holidaysunique"> >+ <thead> >+ <tr> >+ <th class="float">Date</th> >+ <th class="float">Title</th> >+ </tr> >+ </thead> >+ <tbody> >+ [% FOREACH float_holiday IN FLOAT_HOLIDAYS %] >+ <tr> >+ <td><a href="#doc3" onclick="go_to_date('[% float_holiday.date %]')"><span title="[% float_holiday.DATE_SORT %]">[% float_holiday.outputdate %]</span></a></td> >+ <td>[% float_holiday.note %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+</table> >+[% END %] >+</div> >+</div> >+</div> >+</div> >+</div> >+ >+<div class="yui-b noprint"> >+[% INCLUDE 'tools-menu.inc' %] >+</div> >+</div> >+[% 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 <http://www.gnu.org/licenses>. >+ >+#####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
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 17015
:
53859
|
54318
|
54419
|
59166
|
59167
|
59168
|
59169
|
59170
|
59171
|
59267
|
59268
|
59269
|
59270
|
59271
|
59463
|
59516
|
59517
|
59518
|
59519
|
59520
|
59561
|
59562
|
59586
|
59587
|
59888
|
59889
|
60902
|
60903
|
60904
|
60905
|
60906
|
60986
|
61737
|
62375
|
62376
|
62380
|
63257
|
63282
|
63362
|
63363
|
63364
|
63365
|
63366
|
63367
|
64235
|
65073
|
65074
|
65075
|
65076
|
65077
|
67721
|
67722
|
67723
|
67724
|
67725
|
67879
|
67929
|
67930
|
67931
|
67932
|
67933
|
67934
|
68392
|
68393
|
68394
|
68395
|
68396
|
68397
|
71634
|
71635
|
71636
|
71637
|
71638
|
72890
|
73145
|
74859
|
74860
|
74861
|
74862
|
74863
|
74864
|
74865
|
74866
|
75444
|
75479
|
76594
|
77249
|
77250
|
77607
|
77608
|
77609
|
77610
|
77611
|
77612
|
77613
|
77770
|
77771
|
77772
|
77773
|
77774
|
79035
|
80523
|
80524
|
80525
|
80526
|
80527
|
80528
|
80529
|
80530
|
80531
|
80532
|
80533
|
80534
|
80535
|
83547
|
85394
|
85677
|
85678
|
85679
|
85680
|
85681
|
85682
|
85683
|
85684
|
85685
|
85686
|
85687
|
85688
|
85689
|
85690
|
85691
|
92595
|
92596
|
92597
|
92598
|
100079
|
110383
|
110384
|
110386
|
110387
|
110388
|
110389
|
113541
|
113905
|
115501
|
115502
|
115503
|
115504
|
115505
|
115506
|
115507
|
115508
|
115509
|
115510
|
115511
|
118554
|
118555
|
118556
|
118557
|
118558
|
118559
|
119095
|
119097
|
119099
|
119100
|
119101
|
119102
|
131619
|
131620
|
131621
|
131622
|
131623
|
131624
|
131625
|
131626
|
131634
|
131635
|
131636
|
131637
|
131638
|
131639
|
131640
|
131641
|
131667
|
132199
|
133596
|
133597
|
133598
|
133599
|
133600
|
133601
|
133602
|
133603
|
133604
|
133605
|
133678
|
137219
|
137220
|
137221
|
137222
|
137223
|
137224
|
137225
|
137226
|
137227
|
137228
|
137229
|
139378
|
139379
|
139380
|
139381
|
139382
|
139599
|
139600
|
139601
|
139602
|
139603
|
139851
|
140150
|
141176
|
144257
|
144258
|
144259
|
144260
|
144261
|
144262
|
144264
|
144268
|
144269
|
144270
|
144271
|
144272
|
144273
|
151438
|
151439
|
151440
|
151441
|
151442
|
151443
|
151444
|
151445
|
151446
|
151447
|
151448
|
151449
|
151450
|
156340
|
156341
|
156342
|
156343
|
156344
|
156345
|
156346
|
156347
|
156348
|
156349
|
156350
|
156351
|
156352
|
156353
|
156354
|
156355
|
157656
|
157657
|
157658
|
157659
|
157660
|
157661
|
157662
|
157663
|
157664
|
157665
|
157666
|
157667
|
157668
|
157669
|
157670
|
157671
|
157672
|
167805
|
167806
|
167807
|
167808
|
167809