@@ -, +, @@ works as expected. --- C4/Calendar.pm | 153 ++++------ C4/Circulation.pm | 2 + Koha/Calendar.pm | 331 +++++++++++---------- .../data/mysql/de-DE/optional/sample_holidays.sql | 10 +- .../data/mysql/en/optional/sample_holidays.sql | 8 +- .../data/mysql/es-ES/optional/sample_holidays.sql | 8 +- .../data/mysql/it-IT/necessari/sample_holidays.sql | 10 +- installer/data/mysql/kohastructure.sql | 70 +++-- .../mysql/nb-NO/2-Valgfritt/sample_holidays.sql | 14 +- .../data/mysql/pl-PL/optional/sample_holidays.sql | 8 +- installer/data/mysql/ru-RU/optional/holidays.sql | 30 +- installer/data/mysql/uk-UA/optional/holidays.sql | 42 +-- installer/data/mysql/updatedatabase.pl | 76 +++++ .../intranet-tmpl/prog/en/includes/tools-menu.inc | 2 +- .../prog/en/modules/tools/calendar.tt | 56 ++-- misc/cronjobs/staticfines.pl | 2 +- t/Calendar.t | 139 +++++++-- t/db_dependent/Calendar.t | 88 +++--- tools/calendar.pl | 105 +++---- 19 files changed, 648 insertions(+), 506 deletions(-) --- a/C4/Calendar.pm +++ a/C4/Calendar.pm @@ -20,7 +20,7 @@ use vars qw($VERSION @EXPORT); use Carp; -use Koha::Database; +use C4::Context; our ( @ISA, @EXPORT ); @@ -66,10 +66,9 @@ sub GetSingleEvents { return C4::Context->dbh->selectall_arrayref( q{ SELECT - CONCAT(LPAD(year, 4, '0'), '-', LPAD(month, 2, '0'), '-', LPAD(day, 2, '0')) as event_date, - 0 as open_hour, 0 as open_minute, IF(isexception, 24, 0) as close_hour, - 0 as close_minute, title, description, IF(isexception, 0, 1) as closed - FROM special_holidays + event_date, open_hour, open_minute, close_hour, close_minute, title, description, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_events WHERE branchcode = ? }, { Slice => {} }, $branchcode ); } @@ -87,11 +86,11 @@ sub GetWeeklyEvents { return C4::Context->dbh->selectall_arrayref( q{ SELECT - weekday, 0 as open_hour, 0 as open_minute, 0 as close_hour, - 0 as close_minute, title, description, 1 as closed - FROM repeatable_holidays + weekday, open_hour, open_minute, close_hour, close_minute, title, description, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_repeats WHERE branchcode = ? AND weekday IS NOT NULL - }, { Slice => {} }, $branchcode ); + }, { Slice => {} }, $branchcode ); } =head2 GetYearlyEvents @@ -107,130 +106,91 @@ sub GetYearlyEvents { return C4::Context->dbh->selectall_arrayref( q{ SELECT - month, day, 0 as open_hour, 0 as open_minute, 0 as close_hour, - 0 as close_minute, title, description, 1 as closed - FROM repeatable_holidays + month, day, open_hour, open_minute, close_hour, close_minute, title, description, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_repeats WHERE branchcode = ? AND weekday IS NULL }, { Slice => {} }, $branchcode ); } =head2 ModSingleEvent - ModSingleEvent( $branchcode, \%info ) + ModSingleEvent( $branchcode, $date, \%info ) -Creates or updates an event for a single date. $info->{date} should be an -ISO-formatted date string, and \%info should also contain the following keys: -open_hour, open_minute, close_hour, close_minute, title and description. +Creates or updates an event for a single date. $date should be an ISO-formatted +date string, and \%info should contain the following keys: open_hour, +open_minute, close_hour, close_minute, title and description. =cut sub ModSingleEvent { - my ( $branchcode, $info ) = @_; - - my ( $year, $month, $day ) = ( $info->{date} =~ /(\d+)-(\d+)-(\d+)/ ); - return unless ( $year && $month && $day ); - - my $dbh = C4::Context->dbh; - my @args = ( ( map { $info->{$_} } qw(title description) ), $info->{close_hour} != 0, $branchcode, $year, $month, $day ); - - # The code below relies on $dbh->do returning 0 when the update affects no rows - my $affected = $dbh->do( q{ - UPDATE special_holidays - SET - title = ?, description = ?, isexception = ? - WHERE branchcode = ? AND year = ? AND month = ? AND day = ? - }, {}, @args ); - - $dbh->do( q{ - INSERT - INTO special_holidays(title, description, isexception, branchcode, year, month, day) - VALUES (?, ?, ?, ?, ?, ?, ?) - }, {}, @args ) unless ( $affected > 0 ); + my ( $branchcode, $date, $info ) = @_; + + C4::Context->dbh->do( q{ + INSERT INTO calendar_events(branchcode, event_date, open_hour, open_minute, close_hour, close_minute, title, description) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE open_hour = ?, open_minute = ?, close_hour = ?, close_minute = ?, title = ?, description = ? + }, {}, $branchcode, $date, ( map { $info->{$_} } qw(open_hour open_minute close_hour close_minute title description) ) x 2 ); } =head2 ModRepeatingEvent - ModRepeatingEvent( $branchcode, \%info ) + ModRepeatingEvent( $branchcode, $weekday, $month, $day, \%info ) -Creates or updates a weekly- or yearly-repeating event. Either $info->{weekday}, -or $info->{month} and $info->{day} should be set, for a weekly or yearly event, -respectively. +Creates or updates a weekly- or yearly-repeating event. Either $weekday, +or $month and $day should be set, for a weekly or yearly event, respectively. =cut -sub _get_compare { - my ( $colname, $value ) = @_; - - return ' AND ' . $colname . ' ' . ( defined( $value ) ? '=' : 'IS' ) . ' ?'; -} - sub ModRepeatingEvent { - my ( $branchcode, $info ) = @_; - - my $dbh = C4::Context->dbh; - my $open = ( $info->{close_hour} != 0 ); - - if ($open) { - $dbh->do( q{ - DELETE FROM repeatable_holidays - WHERE branchcode = ? - } . _get_compare( 'weekday', $info->{weekday} ) . _get_compare( 'month', $info->{month} ) . _get_compare( 'day', $info->{day} ), {}, $branchcode, $info->{weekday}, $info->{month}, $info->{day} ); - } else { - my @args = ( ( map { $info->{$_} } qw(title description) ), $branchcode, $info->{weekday}, $info->{month}, $info->{day} ); - - # The code below relies on $dbh->do returning 0 when the update affects no rows - my $affected = $dbh->do( q{ - UPDATE repeatable_holidays - SET - title = ?, description = ? - WHERE branchcode = ? - } . _get_compare( 'weekday', $info->{weekday} ) . _get_compare( 'month', $info->{month} ) . _get_compare( 'day', $info->{day} ), {}, @args ); - - $dbh->do( q{ - INSERT - INTO repeatable_holidays(title, description, branchcode, weekday, month, day) - VALUES (?, ?, ?, ?, ?, ?) - }, {}, @args ) unless ( $affected > 0 ); - } + my ( $branchcode, $weekday, $month, $day, $info ) = @_; + + C4::Context->dbh->do( q{ + INSERT INTO calendar_repeats(branchcode, weekday, month, day, open_hour, open_minute, close_hour, close_minute, title, description) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE open_hour = ?, open_minute = ?, close_hour = ?, close_minute = ?, title = ?, description = ? + }, {}, $branchcode, $weekday, $month, $day, ( map { $info->{$_} } qw(open_hour open_minute close_hour close_minute title description) ) x 2 ); } =head2 DelSingleEvent - DelSingleEvent( $branchcode, \%info ) + DelSingleEvent( $branchcode, $date, \%info ) -Deletes an event for a single date. $info->{date} should be an ISO-formatted date string. +Deletes an event for a single date. $date should be an ISO-formatted date string. =cut sub DelSingleEvent { - my ( $branchcode, $info ) = @_; - - my ( $year, $month, $day ) = ( $info->{date} =~ /(\d+)-(\d+)-(\d+)/ ); - return unless ( $year && $month && $day ); + my ( $branchcode, $date ) = @_; C4::Context->dbh->do( q{ - DELETE FROM special_holidays - WHERE branchcode = ? AND year = ? AND month = ? AND day = ? - }, {}, $branchcode, $year, $month, $day ); + DELETE FROM calendar_events + WHERE branchcode = ? AND event_date = ? + }, {}, $branchcode, $date ); +} + +sub _get_compare { + my ( $colname, $value ) = @_; + + return ' AND ' . $colname . ' ' . ( defined( $value ) ? '=' : 'IS' ) . ' ?'; } =head2 DelRepeatingEvent - DelRepeatingEvent( $branchcode, \%info ) + DelRepeatingEvent( $branchcode, $weekday, $month, $day ) -Deletes a weekly- or yearly-repeating event. Either $info->{weekday}, or -$info->{month} and $info->{day} should be set, for a weekly or yearly event, -respectively. +Deletes a weekly- or yearly-repeating event. Either $weekday, or $month and +$day should be set, for a weekly or yearly event, respectively. =cut sub DelRepeatingEvent { - my ( $branchcode, $info ) = @_; + my ( $branchcode, $weekday, $month, $day ) = @_; C4::Context->dbh->do( q{ - DELETE FROM repeatable_holidays + DELETE FROM calendar_repeats WHERE branchcode = ? - } . _get_compare( 'weekday', $info->{weekday} ) . _get_compare( 'month', $info->{month} ) . _get_compare( 'day', $info->{day} ), {}, $branchcode, $info->{weekday}, $info->{month}, $info->{day} ); + } . _get_compare( 'weekday', $weekday ) . _get_compare( 'month', $month ) . _get_compare( 'day', $day ), {}, $branchcode, $weekday, $month, $day ); } =head2 CopyAllEvents @@ -245,16 +205,16 @@ sub CopyAllEvents { my ( $from_branchcode, $to_branchcode ) = @_; C4::Context->dbh->do( q{ - INSERT IGNORE INTO special_holidays(branchcode, year, month, day, isexception, title, description) - SELECT ?, year, month, day, isexception, title, description - FROM special_holidays + INSERT IGNORE INTO calendar_events(branchcode, event_date, open_hour, open_minute, close_hour, close_minute, title, description) + SELECT ?, event_date, open_hour, open_minute, close_hour, close_minute, title, description + FROM calendar_events WHERE branchcode = ? }, {}, $to_branchcode, $from_branchcode ); C4::Context->dbh->do( q{ - INSERT IGNORE INTO repeatable_holidays(branchcode, weekday, month, day, title, description) - SELECT ?, weekday, month, day, title, description - FROM repeatable_holidays + INSERT IGNORE INTO calendar_repeats(branchcode, weekday, month, day, open_hour, open_minute, close_hour, close_minute, title, description) + SELECT ?, weekday, month, day, open_hour, open_minute, close_hour, close_minute, title, description + FROM calendar_repeats WHERE branchcode = ? }, {}, $to_branchcode, $from_branchcode ); } @@ -267,6 +227,5 @@ __END__ =head1 AUTHOR Koha Physics Library UNLP -Jesse Weaver =cut --- a/C4/Circulation.pm +++ a/C4/Circulation.pm @@ -3337,6 +3337,8 @@ sub CalcDateDue { return $datedue; } + + sub CheckValidBarcode{ my ($barcode) = @_; my $dbh = C4::Context->dbh; --- a/Koha/Calendar.pm +++ a/Koha/Calendar.pm @@ -29,91 +29,41 @@ sub _init { my $self = shift; my $branch = $self->{branchcode}; my $dbh = C4::Context->dbh(); - my $weekly_closed_days_sth = $dbh->prepare( -'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL' - ); - $weekly_closed_days_sth->execute( $branch ); - $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; - Readonly::Scalar my $sunday => 7; - while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) { - $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1; - } - my $day_month_closed_days_sth = $dbh->prepare( -'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL' - ); - $day_month_closed_days_sth->execute( $branch ); - $self->{day_month_closed_days} = {}; - while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) { - $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } = - 1; + + $self->{weekday_hours} = $dbh->selectall_hashref( q{ + SELECT + weekday, open_hour, open_minute, close_hour, close_minute, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_repeats + WHERE branchcode = ? AND weekday IS NOT NULL + }, 'weekday', { Slice => {} }, $branch ); + + my $day_month_hours = $dbh->selectall_arrayref( q{ + SELECT + month, day, open_hour, open_minute, close_hour, close_minute, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_repeats + WHERE branchcode = ? AND weekday IS NULL + }, { Slice => {} }, $branch ); + + # DBD::Mock doesn't support multi-key selectall_hashref, so we do it ourselves for now + foreach my $day_month ( @$day_month_hours ) { + $self->{day_month_hours}->{ $day_month->{month} }->{ $day_month->{day} } = $day_month; } + $self->{date_hours} = $dbh->selectall_hashref( q{ + SELECT + event_date, open_hour, open_minute, close_hour, close_minute, + (open_hour = 0 AND open_minute = 0 AND close_hour = 0 AND close_minute = 0) AS closed + FROM calendar_events + WHERE branchcode = ? + }, 'event_date', { Slice => {} }, $branch ); + $self->{days_mode} = C4::Context->preference('useDaysMode'); $self->{test} = 0; return; } - -# FIXME: use of package-level variables for caching the holiday -# lists breaks persistance engines. As of 2013-12-10, the RM -# is allowing this with the expectation that prior to release of -# 3.16, bug 8089 will be fixed and we can switch the caching over -# to Koha::Cache. -our ( $exception_holidays, $single_holidays ); -sub _exception_holidays { - my ( $self ) = @_; - my $dbh = C4::Context->dbh; - my $branch = $self->{branchcode}; - if ( $exception_holidays ) { - $self->{exception_holidays} = $exception_holidays; - return $exception_holidays; - } - my $exception_holidays_sth = $dbh->prepare( -'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 1' - ); - $exception_holidays_sth->execute( $branch ); - my $dates = []; - while ( my ( $day, $month, $year ) = $exception_holidays_sth->fetchrow ) { - push @{$dates}, - DateTime->new( - day => $day, - month => $month, - year => $year, - time_zone => C4::Context->tz() - )->truncate( to => 'day' ); - } - $self->{exception_holidays} = - DateTime::Set->from_datetimes( dates => $dates ); - $exception_holidays = $self->{exception_holidays}; - return $exception_holidays; -} - -sub _single_holidays { - my ( $self ) = @_; - my $dbh = C4::Context->dbh; - my $branch = $self->{branchcode}; - if ( $single_holidays ) { - $self->{single_holidays} = $single_holidays; - return $single_holidays; - } - my $single_holidays_sth = $dbh->prepare( -'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 0' - ); - $single_holidays_sth->execute( $branch ); - my $dates = []; - while ( my ( $day, $month, $year ) = $single_holidays_sth->fetchrow ) { - push @{$dates}, - DateTime->new( - day => $day, - month => $month, - year => $year, - time_zone => C4::Context->tz() - )->truncate( to => 'day' ); - } - $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates ); - $single_holidays = $self->{single_holidays}; - return $single_holidays; -} sub addDate { my ( $self, $startdate, $add_duration, $unit ) = @_; @@ -126,10 +76,7 @@ sub addDate { my $dt; if ( $unit eq 'hours' ) { - # Fixed for legacy support. Should be set as a branch parameter - Readonly::Scalar my $return_by_hour => 10; - - $dt = $self->addHours($startdate, $add_duration, $return_by_hour); + $dt = $self->addHours($startdate, $add_duration); } else { # days $dt = $self->addDays($startdate, $add_duration); @@ -139,25 +86,73 @@ sub addDate { } sub addHours { - my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_; + my ( $self, $startdate, $hours_duration ) = @_; my $base_date = $startdate->clone(); - $base_date->add_duration($hours_duration); + if ( $self->{days_mode} eq 'Days' ) { + $base_date->add_duration( $hours_duration ); + return $base_date; + } + my $hours = $self->get_hours_full( $base_date ); + + if ( $hours_duration->is_negative() ) { + if ( $base_date <= $hours->{open_time} ) { + # Library is already closed + $base_date = $self->prev_open_day( $base_date ); + $hours = $self->get_hours_full( $base_date ); + $base_date = $hours->{close_time}->clone; + + if ( $self->{days_mode} eq 'Calendar' ) { + return $base_date; + } + } - # If we are using the calendar behave for now as if Datedue - # was the chosen option (current intended behaviour) + while ( $hours_duration->is_negative ) { + my $day_len = $hours->{open_time} - $base_date; - if ( $self->{days_mode} ne 'Days' && - $self->is_holiday($base_date) ) { + if ( DateTime::Duration->compare( $day_len, $hours_duration, $base_date ) > 0 ) { + if ( $self->{days_mode} eq 'Calendar' ) { + return $hours->{open_time}; + } - if ( $hours_duration->is_negative() ) { - $base_date = $self->prev_open_day($base_date); - } else { - $base_date = $self->next_open_day($base_date); + $hours_duration->subtract( $day_len ); + $base_date = $self->prev_open_day( $base_date ); + $hours = $self->get_hours_full( $base_date ); + $base_date = $hours->{close_time}->clone; + } else { + $base_date->add_duration( $hours_duration ); + return $base_date; + } + } + } else { + if ( $base_date >= $hours->{close_time} ) { + # Library is already closed + $base_date = $self->next_open_day( $base_date ); + $hours = $self->get_hours_full( $base_date ); + $base_date = $hours->{open_time}->clone; + + if ( $self->{days_mode} eq 'Calendar' ) { + return $base_date; + } } - $base_date->set_hour($return_by_hour); + while ( $hours_duration->is_positive ) { + my $day_len = $hours->{close_time} - $base_date; + + if ( DateTime::Duration->compare( $day_len, $hours_duration, $base_date ) < 0 ) { + if ( $self->{days_mode} eq 'Calendar' ) { + return $hours->{close_time}; + } + $hours_duration->subtract( $day_len ); + $base_date = $self->next_open_day( $base_date ); + $hours = $self->get_hours_full( $base_date ); + $base_date = $hours->{open_time}->clone; + } else { + $base_date->add_duration( $hours_duration ); + return $base_date; + } + } } return $base_date; @@ -207,33 +202,25 @@ sub addDays { sub is_holiday { my ( $self, $dt ) = @_; - my $localdt = $dt->clone(); - my $day = $localdt->day; - my $month = $localdt->month; - - $localdt->truncate( to => 'day' ); + my $day = $dt->day; + my $month = $dt->month; - if ( $self->_exception_holidays->contains($localdt) ) { - # exceptions are not holidays + if ( exists $self->{date_hours}->{ $dt->ymd } && !$self->{date_hours}->{ $dt->ymd }->{closed} ) { return 0; } - my $dow = $localdt->day_of_week; - # Representation fix - # TODO: Shouldn't we shift the rest of the $dow also? - if ( $dow == 7 ) { - $dow = 0; - } - - if ( $self->{weekly_closed_days}->[$dow] == 1 ) { + if ( ( $self->{day_month_hours}->{$month}->{$day} || {} )->{closed} ) { return 1; } - if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) { + # We use 0 for Sunday, not 7 + my $dow = $dt->day_of_week % 7; + + if ( ( $self->{weekday_hours}->{ $dow } || {} )->{closed} ) { return 1; } - if ( $self->_single_holidays->contains($localdt) ) { + if ( ( $self->{date_hours}->{ $dt->ymd } || {} )->{closed} ) { return 1; } @@ -241,6 +228,60 @@ sub is_holiday { return 0; } +sub get_hours { + my ( $self, $dt ) = @_; + my $day = $dt->day; + my $month = $dt->month; + + if ( exists $self->{date_hours}->{ $dt->ymd } ) { + return $self->{date_hours}->{ $dt->ymd }; + } + + if ( exists $self->{day_month_hours}->{$month}->{$day} ) { + return $self->{day_month_hours}->{$month}->{$day}; + } + + # We use 0 for Sunday, not 7 + my $dow = $dt->day_of_week % 7; + + if ( exists $self->{weekday_hours}->{ $dow } ) { + return $self->{weekday_hours}->{ $dow }; + } + + # Assume open + return { + open_hour => 0, + open_minute => 0, + close_hour => 24, + close_minute => 0, + closed => 0 + }; +} + +sub get_hours_full { + my ( $self, $dt ) = @_; + + my $hours = { %{ $self->get_hours( $dt ) } }; + + $hours->{open_time} = $dt + ->clone->truncate( to => 'day' ) + ->set_hour( $hours->{open_hour} ) + ->set_minute( $hours->{open_minute} ); + + if ( $hours->{close_hour} == 24 ) { + $hours->{close_time} = $dt + ->clone->truncate( to => 'day' ) + ->add( days => 1 ); + } else { + $hours->{close_time} = $dt + ->clone->truncate( to => 'day' ) + ->set_hour( $hours->{close_hour} ) + ->set_minute( $hours->{close_minute} ); + } + + return $hours; +} + sub next_open_day { my ( $self, $dt ) = @_; my $base_date = $dt->clone(); @@ -298,45 +339,39 @@ sub hours_between { my ($self, $start_date, $end_date) = @_; my $start_dt = $start_date->clone(); my $end_dt = $end_date->clone(); - my $duration = $end_dt->delta_ms($start_dt); - $start_dt->truncate( to => 'day' ); - $end_dt->truncate( to => 'day' ); - # NB this is a kludge in that it assumes all days are 24 hours - # However for hourly loans the logic should be expanded to - # take into account open/close times then it would be a duration - # of library open hours - my $skipped_days = 0; - for (my $dt = $start_dt->clone(); - $dt <= $end_dt; - $dt->add(days => 1) - ) { - if ($self->is_holiday($dt)) { - ++$skipped_days; - } - } - if ($skipped_days) { - $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days)); + + if ( $start_dt->compare($end_dt) > 0 ) { + # swap dates + my $int_dt = $end_dt; + $end_dt = $start_dt; + $start_dt = $int_dt; } - return $duration; + my $start_hours = $self->get_hours_full( $start_dt ); + my $end_hours = $self->get_hours_full( $end_dt ); -} + $start_dt = $start_hours->{open_time} if ( $start_dt < $start_hours->{open_time} ); + $end_dt = $end_hours->{close_time} if ( $end_dt > $end_hours->{close_time} ); + + return $end_dt - $start_dt if ( $start_dt->ymd eq $end_dt->ymd ); -sub set_daysmode { - my ( $self, $mode ) = @_; + my $duration = DateTime::Duration->new; + + $duration->add_duration( $start_hours->{close_time} - $start_dt ) if ( $start_dt < $start_hours->{close_time} ); - # if not testing this is a no op - if ( $self->{test} ) { - $self->{days_mode} = $mode; + for (my $date = $start_dt->clone->truncate( to => 'day' )->add( days => 1 ); + $date->ymd lt $end_dt->ymd; + $date->add(days => 1) + ) { + my $hours = $self->get_hours_full( $date ); + + $duration->add_duration( $hours->{close_time}->delta_ms( $hours->{open_time} ) ); } - return; -} + $duration->add_duration( $end_dt - $end_hours->{open_time} ) if ( $end_dt > $start_hours->{open_time} ); + + return $duration; -sub clear_weekly_closed_days { - my $self = shift; - $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ]; # Sunday only - return; } 1; @@ -392,14 +427,12 @@ parameter will be removed when issuingrules properly cope with that =head2 addHours - my $dt = $calendar->addHours($date, $dur, $return_by_hour ) + my $dt = $calendar->addHours($date, $dur ) C<$date> is a DateTime object representing the starting date of the interval. C<$offset> is a DateTime::Duration to add to it -C<$return_by_hour> is an integer value representing the opening hour for the branch - =head2 addDays @@ -446,16 +479,6 @@ Passed a Datetime returns another Datetime representing the previous open day. I intended for use to calculate the due date when useDaysMode syspref is set to either 'Datedue' or 'Calendar'. -=head2 set_daysmode - -For testing only allows the calling script to change days mode - -=head2 clear_weekly_closed_days - -In test mode changes the testing set of closed days to a new set with -no closed days. TODO passing an array of closed days to this would -allow testing of more configurations - =head1 DIAGNOSTICS Will croak if not passed a branchcode in new --- a/installer/data/mysql/de-DE/optional/sample_holidays.sql +++ a/installer/data/mysql/de-DE/optional/sample_holidays.sql @@ -1,5 +1,5 @@ -INSERT INTO `repeatable_holidays` VALUES -(2,'MPL',0,NULL,NULL,'','Sonntag'), -(3,'MPL',NULL,1,1,'','Neujahr'), -(4,'MPL',NULL,25,12,'','1. Weihnachtsfeiertag'), -(5,'MPL',NULL,25,12,'','2. Weihnachtsfeiertag'); +INSERT INTO `calendar_repeats` VALUES +(2,'MPL',0,NULL,NULL,'','Sonntag',0,0,0,0), +(3,'MPL',NULL,1,1,'','Neujahr',0,0,0,0), +(4,'MPL',NULL,12,25,'','1. Weihnachtsfeiertag',0,0,0,0), +(5,'MPL',NULL,12,25,'','2. Weihnachtsfeiertag',0,0,0,0); --- a/installer/data/mysql/en/optional/sample_holidays.sql +++ a/installer/data/mysql/en/optional/sample_holidays.sql @@ -1,4 +1,4 @@ -INSERT INTO `repeatable_holidays` VALUES -(2,'MPL',0,NULL,NULL,'','Sundays'), -(3,'MPL',NULL,1,1,'','New Year\'s Day'), -(4,'MPL',NULL,25,12,'','Christmas'); +INSERT INTO `calendar_repeats` VALUES +(2,'MPL',0,NULL,NULL,'','Sundays',0,0,0,0), +(3,'MPL',NULL,1,1,'','New Year\'s Day',0,0,0,0), +(4,'MPL',NULL,12,25,'','Christmas',0,0,0,0); --- a/installer/data/mysql/es-ES/optional/sample_holidays.sql +++ a/installer/data/mysql/es-ES/optional/sample_holidays.sql @@ -1,4 +1,4 @@ -INSERT INTO `repeatable_holidays` VALUES -(2,'',0,NULL,NULL,'','Sundays'), -(3,'',NULL,1,1,'','New Year\'s Day'), -(4,'',NULL,25,12,'','Christmas'); +INSERT INTO `calendar_repeats` VALUES +(2,'',0,NULL,NULL,'','Sundays',0,0,0,0), +(3,'',NULL,1,1,'','New Year\'s Day',0,0,0,0), +(4,'',NULL,12,25,'','Christmas',0,0,0,0); --- a/installer/data/mysql/it-IT/necessari/sample_holidays.sql +++ a/installer/data/mysql/it-IT/necessari/sample_holidays.sql @@ -1,8 +1,8 @@ SET FOREIGN_KEY_CHECKS=0; -INSERT INTO `repeatable_holidays` VALUES -(2,'',0,NULL,NULL,'','Domenica'), -(3,'',NULL,1,1,'','Nuovo anno'), -(4,'',NULL,25,12,'','Natale'); +INSERT INTO `calendar_repeats` VALUES +(2,'',0,NULL,NULL,'','Domenica',0,0,0,0), +(3,'',NULL,1,1,'','Nuovo anno',0,0,0,0), +(4,'',NULL,12,25,'','Natale',0,0,0,0); -SET FOREIGN_KEY_CHECKS=1; +SET FOREIGN_KEY_CHECKS=1; --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -667,6 +667,43 @@ CREATE TABLE `default_circ_rules` ( PRIMARY KEY (`singleton`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `calendar_events` +-- +DROP TABLE IF EXISTS `calendar_events`; +CREATE TABLE `calendar_events` ( + `branchcode` varchar(10) NOT NULL DEFAULT '', + `event_date` date NOT NULL, + `title` varchar(50) NOT NULL DEFAULT '', + `description` text NOT NULL, + `open_hour` smallint(6) NOT NULL, + `open_minute` smallint(6) NOT NULL, + `close_hour` smallint(6) NOT NULL, + `close_minute` smallint(6) NOT NULL, + PRIMARY KEY (`branchcode`,`event_date`), + CONSTRAINT `calendar_events_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- +-- Table structure for table `calendar_repeats` +-- +DROP TABLE IF EXISTS `calendar_repeats`; +CREATE TABLE `calendar_repeats` ( + `branchcode` varchar(10) NOT NULL DEFAULT '', + `weekday` smallint(6) DEFAULT NULL, + `month` smallint(6) DEFAULT NULL, + `day` smallint(6) DEFAULT NULL, + `title` varchar(50) NOT NULL DEFAULT '', + `description` text NOT NULL, + `open_hour` smallint(6) NOT NULL, + `open_minute` smallint(6) NOT NULL, + `close_hour` smallint(6) NOT NULL, + `close_minute` smallint(6) NOT NULL, + UNIQUE KEY `branchcode` (`branchcode`,`weekday`), + UNIQUE KEY `branchcode_2` (`branchcode`,`month`,`day`), + CONSTRAINT `calendar_repeats_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Table structure for table `cities` -- @@ -1766,22 +1803,6 @@ CREATE TABLE `printers_profile` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- --- Table structure for table `repeatable_holidays` --- - -DROP TABLE IF EXISTS `repeatable_holidays`; -CREATE TABLE `repeatable_holidays` ( -- information for the days the library is closed - `id` int(11) NOT NULL auto_increment, -- unique identifier assigned by Koha - `branchcode` varchar(10) NOT NULL default '', -- foreign key from the branches table, defines which branch this closing is for - `weekday` smallint(6) default NULL, -- day of the week (0=Sunday, 1=Monday, etc) this closing is repeated on - `day` smallint(6) default NULL, -- day of the month this closing is on - `month` smallint(6) default NULL, -- month this closing is in - `title` varchar(50) NOT NULL default '', -- title of this closing - `description` text NOT NULL, -- description for this closing - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- -- Table structure for table `reports_dictionary` -- @@ -1954,23 +1975,6 @@ CREATE TABLE sessions ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- --- Table structure for table `special_holidays` --- - -DROP TABLE IF EXISTS `special_holidays`; -CREATE TABLE `special_holidays` ( -- non repeatable holidays/library closings - `id` int(11) NOT NULL auto_increment, -- unique identifier assigned by Koha - `branchcode` varchar(10) NOT NULL default '', -- foreign key from the branches table, defines which branch this closing is for - `day` smallint(6) NOT NULL default 0, -- day of the month this closing is on - `month` smallint(6) NOT NULL default 0, -- month this closing is in - `year` smallint(6) NOT NULL default 0, -- year this closing is in - `isexception` smallint(1) NOT NULL default 1, -- is this a holiday exception to a repeatable holiday (1 for yes, 0 for no) - `title` varchar(50) NOT NULL default '', -- title for this closing - `description` text NOT NULL, -- description of this closing - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- -- Table structure for table `statistics` -- --- a/installer/data/mysql/nb-NO/2-Valgfritt/sample_holidays.sql +++ a/installer/data/mysql/nb-NO/2-Valgfritt/sample_holidays.sql @@ -19,10 +19,10 @@ -- with Koha; if not, write to the Free Software Foundation, Inc., -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -INSERT INTO `repeatable_holidays` VALUES -(2,'',0,NULL,NULL,'','Søndager'), -(3,'',NULL,1,1,'','1. nyttårsdag'), -(4,'',NULL,1,5,'','1. mai'), -(5,'',NULL,17,5,'','17. mai'), -(6,'',NULL,25,12,'','1. juledag'), -(7,'',NULL,26,12,'','2. juledag'); +INSERT INTO `calendar_repeats` VALUES +(2,'',0,NULL,NULL,'','Søndager',0,0,0,0), +(3,'',NULL,1,1,'','1. nyttårsdag',0,0,0,0), +(4,'',NULL,5,1,'','1. mai',0,0,0,0), +(5,'',NULL,5,17,'','17. mai',0,0,0,0), +(6,'',NULL,12,25,'','1. juledag',0,0,0,0), +(7,'',NULL,12,26,'','2. juledag',0,0,0,0); --- a/installer/data/mysql/pl-PL/optional/sample_holidays.sql +++ a/installer/data/mysql/pl-PL/optional/sample_holidays.sql @@ -1,4 +1,4 @@ -INSERT INTO `repeatable_holidays` VALUES -(2,'',0,NULL,NULL,'','Niedziele'), -(3,'',NULL,1,1,'','Nowy Rok'), -(4,'',NULL,25,12,'','Boże Narodzenie'); +INSERT INTO `calendar_repeats` VALUES +(2,'',0,NULL,NULL,'','Niedziele',0,0,0,0), +(3,'',NULL,1,1,'','Nowy Rok',0,0,0,0), +(4,'',NULL,12,25,'','Boże Narodzenie',0,0,0,0); --- a/installer/data/mysql/ru-RU/optional/holidays.sql +++ a/installer/data/mysql/ru-RU/optional/holidays.sql @@ -1,23 +1,23 @@ -TRUNCATE repeatable_holidays; +TRUNCATE calendar_repeats; -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (2, '',0,NULL,NULL,'','Воскресенья'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',0,NULL,NULL,'','Воскресенья', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (3, '',NULL,1,1,'','Новый год'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,1,1,'','Новый год', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (4, '',NULL,7,1,'','Рождество'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,7,1,'','Рождество', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (5, '',NULL,8,3,'','Международный женский день'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,8,3,'','Международный женский день', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (6, '',NULL,1,5,'','День Труда'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,1,5,'','День Труда', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (7, '',NULL,2,5,'','День Труда'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,2,5,'','День Труда', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`id`, `branchcode`, `weekday`, `day`, `month`, `title`, `description`) -VALUES (8, '',NULL,9,5,'','День Победы'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) +VALUES ('',NULL,9,5,'','День Победы', 0, 0, 0, 0); --- a/installer/data/mysql/uk-UA/optional/holidays.sql +++ a/installer/data/mysql/uk-UA/optional/holidays.sql @@ -1,24 +1,24 @@ -TRUNCATE repeatable_holidays; +TRUNCATE calendar_repeats; -INSERT INTO `repeatable_holidays` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`) VALUES -('STL', 0, NULL, NULL,'', 'Неділі'), -('STL', NULL, 1, 1, '', 'Новий рік'), -('STL', NULL, 7, 1, '', 'Різдво'), -('STL', NULL, 8, 3, '', 'Міжнародний жіночий день'), -('STL', NULL, 1, 5, '', 'День Праці'), -('STL', NULL, 2, 5, '', 'День Праці'), -('STL', NULL, 9, 5, '', 'День Перемоги'), -('STL', NULL, 28, 6, '', 'День Конституції'), -('STL', NULL, 24, 8, '', 'День Незалежності'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) VALUES +('STL', 0, NULL, NULL,'', 'Неділі', 0, 0, 0, 0), +('STL', NULL, 1, 1, '', 'Новий рік', 0, 0, 0, 0), +('STL', NULL, 7, 1, '', 'Різдво', 0, 0, 0, 0), +('STL', NULL, 8, 3, '', 'Міжнародний жіночий день', 0, 0, 0, 0), +('STL', NULL, 1, 5, '', 'День Праці', 0, 0, 0, 0), +('STL', NULL, 2, 5, '', 'День Праці', 0, 0, 0, 0), +('STL', NULL, 9, 5, '', 'День Перемоги', 0, 0, 0, 0), +('STL', NULL, 28, 6, '', 'День Конституції', 0, 0, 0, 0), +('STL', NULL, 24, 8, '', 'День Незалежності', 0, 0, 0, 0); -INSERT INTO `repeatable_holidays` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`) VALUES -('LNSL', 0, NULL, NULL,'', 'Неділі'), -('LNSL', NULL, 1, 1, '', 'Новий рік'), -('LNSL', NULL, 7, 1, '', 'Різдво'), -('LNSL', NULL, 8, 3, '', 'Міжнародний жіночий день'), -('LNSL', NULL, 1, 5, '', 'День Праці'), -('LNSL', NULL, 2, 5, '', 'День Праці'), -('LNSL', NULL, 9, 5, '', 'День Перемоги'), -('LNSL', NULL, 28, 6, '', 'День Конституції'), -('LNSL', NULL, 24, 8, '', 'День Незалежності'); +INSERT INTO `calendar_repeats` (`branchcode`, `weekday`, `day`, `month`, `title`, `description`, open_hour, open_minute, close_hour, close_minute) VALUES +('LNSL', 0, NULL, NULL,'', 'Неділі', 0, 0, 0, 0), +('LNSL', NULL, 1, 1, '', 'Новий рік', 0, 0, 0, 0), +('LNSL', NULL, 7, 1, '', 'Різдво', 0, 0, 0, 0), +('LNSL', NULL, 8, 3, '', 'Міжнародний жіночий день', 0, 0, 0, 0), +('LNSL', NULL, 1, 5, '', 'День Праці', 0, 0, 0, 0), +('LNSL', NULL, 2, 5, '', 'День Праці', 0, 0, 0, 0), +('LNSL', NULL, 9, 5, '', 'День Перемоги', 0, 0, 0, 0), +('LNSL', NULL, 28, 6, '', 'День Конституції', 0, 0, 0, 0), +('LNSL', NULL, 24, 8, '', 'День Незалежності', 0, 0, 0, 0); --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -8778,6 +8778,82 @@ if ( CheckVersion($DBversion) ) { INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type) VALUES('StatisticsFields','location|itype|ccode','Define fields (from the items table) used for statistics members',NULL,'Free') }); print "Upgrade to $DBversion done (Bug 12728: Checked syspref StatisticsFields)\n"; + + SetVersion($DBversion); +} + +$DBversion = "3.17.00.XXX"; +if ( CheckVersion($DBversion) ) { + print "Upgrade to $DBversion done (Bug 8133: create tables, migrate data to calendar_*)\n"; + + $dbh->do( q{ + CREATE TABLE `calendar_events` ( + `branchcode` varchar(10) NOT NULL DEFAULT '', + `event_date` date NOT NULL, + `title` varchar(50) NOT NULL DEFAULT '', + `description` text NOT NULL, + `open_hour` smallint(6) NOT NULL, + `open_minute` smallint(6) NOT NULL, + `close_hour` smallint(6) NOT NULL, + `close_minute` smallint(6) NOT NULL, + PRIMARY KEY (`branchcode`,`event_date`), + CONSTRAINT `calendar_events_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + } ); + + $dbh->do( q{ + CREATE TABLE `calendar_repeats` ( + `branchcode` varchar(10) NOT NULL DEFAULT '', + `weekday` smallint(6) DEFAULT NULL, + `month` smallint(6) DEFAULT NULL, + `day` smallint(6) DEFAULT NULL, + `title` varchar(50) NOT NULL DEFAULT '', + `description` text NOT NULL, + `open_hour` smallint(6) NOT NULL, + `open_minute` smallint(6) NOT NULL, + `close_hour` smallint(6) NOT NULL, + `close_minute` smallint(6) NOT NULL, + UNIQUE KEY `branchcode` (`branchcode`,`weekday`), + UNIQUE KEY `branchcode_2` (`branchcode`,`month`,`day`), + CONSTRAINT `calendar_repeats_ibfk_1` FOREIGN KEY (`branchcode`) REFERENCES `branches` (`branchcode`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + } ); + + $dbh->do( q{ + INSERT INTO + calendar_events(branchcode, event_date, title, description, open_hour, open_minute, close_hour, close_minute) + SELECT + branchcode, CONCAT_WS('-', year, month, day), title, description, 0, 0, 0, 0 + FROM special_holidays + WHERE isexception = 0 + } ); + + $dbh->do( q{ + INSERT INTO + calendar_events(branchcode, event_date, title, description, open_hour, open_minute, close_hour, close_minute) + SELECT + branchcode, CONCAT_WS('-', year, month, day), title, description, 0, 0, 24, 0 + FROM special_holidays + WHERE isexception = 1 + } ); + + $dbh->do( q{ + INSERT INTO + calendar_repeats(branchcode, weekday, month, day, title, description, open_hour, open_minute, close_hour, close_minute) + SELECT + branchcode, weekday, month, day, title, description, 0, 0, 0, 0 + FROM repeatable_holidays + WHERE weekday IS NULL + } ); + + $dbh->do( q{ + DROP TABLE repeatable_holidays; + } ); + + $dbh->do( q{ + DROP TABLE special_holidays; + } ); + SetVersion($DBversion); } --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -87,7 +87,7 @@
Additional tools