View | Details | Raw Unified | Return to bug 17015
Collapse All | Expand All

(-)a/C4/Circulation.pm (-8 / +7 lines)
Lines 102-108 use Koha::AuthorisedValues; Link Here
102
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
102
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
103
use Koha::Biblioitems;
103
use Koha::Biblioitems;
104
use Koha::DateUtils qw( dt_from_string );
104
use Koha::DateUtils qw( dt_from_string );
105
use Koha::Calendar;
105
use Koha::DiscreteCalendar;
106
use Koha::Checkouts;
106
use Koha::Checkouts;
107
use Koha::ILL::Requests;
107
use Koha::ILL::Requests;
108
use Koha::Items;
108
use Koha::Items;
Lines 1527-1533 sub checkHighHolds { Link Here
1527
                branchcode   => $branchcode,
1527
                branchcode   => $branchcode,
1528
            }
1528
            }
1529
        );
1529
        );
1530
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
1530
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
1531
1531
1532
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron );
1532
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron );
1533
1533
Lines 2984-2993 sub _calculate_new_debar_dt { Link Here
2984
2984
2985
        # Use the calendar or not to calculate the debarment date
2985
        # Use the calendar or not to calculate the debarment date
2986
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2986
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2987
            my $calendar = Koha::Calendar->new(
2987
            my $calendar = Koha::DiscreteCalendar->new({
2988
                branchcode => $branchcode,
2988
                branchcode => $branchcode,
2989
                days_mode  => 'Calendar'
2989
                days_mode  => 'Calendar'
2990
            );
2990
            });
2991
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2991
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2992
        } else {
2992
        } else {
2993
            $new_debar_dt = $return_date->clone()->add_duration($suspension_days);
2993
            $new_debar_dt = $return_date->clone()->add_duration($suspension_days);
Lines 4266-4272 sub CalcDateDue { Link Here
4266
        } else {    # days
4266
        } else {    # days
4267
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4267
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4268
        }
4268
        }
4269
        my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
4269
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
4270
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} ) if $dur;
4270
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} ) if $dur;
4271
        if ( $loanlength->{lengthunit} eq 'days' ) {
4271
        if ( $loanlength->{lengthunit} eq 'days' ) {
4272
            $datedue->set_hour(23);
4272
            $datedue->set_hour(23);
Lines 4304-4318 sub CalcDateDue { Link Here
4304
            }
4304
            }
4305
        }
4305
        }
4306
        if ( $daysmode ne 'Days' ) {
4306
        if ( $daysmode ne 'Days' ) {
4307
            my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
4307
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
4308
            if ( $calendar->is_holiday($datedue) ) {
4308
            if ( $calendar->is_holiday($datedue) ) {
4309
4309
4310
                # Don't return on a closed day
4310
                # Don't return on a closed day
4311
                $datedue = $calendar->prev_open_days( $datedue, 1 );
4311
                $datedue = $calendar->prev_open_day( $datedue )->set(hour => 23, minute => 59);
4312
            }
4312
            }
4313
        }
4313
        }
4314
    }
4314
    }
4315
4316
    return $datedue;
4315
    return $datedue;
4317
}
4316
}
4318
4317
(-)a/C4/HoldsQueue.pm (-1 / +8 lines)
Lines 77-82 sub TransportCostMatrix { Link Here
77
            cost             => $cost,
77
            cost             => $cost,
78
            disable_transfer => $disabled
78
            disable_transfer => $disabled
79
        };
79
        };
80
81
        if ( !my $ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
82
            my $calendars->{$from} ||= Koha::DiscreteCalendar->new({ branchcode => $from });
83
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
84
              $calendars->{$from}->is_holiday( $today );
85
        }
86
80
    }
87
    }
81
88
82
    return \%transport_cost_matrix;
89
    return \%transport_cost_matrix;
Lines 1163-1169 sub load_branches_to_pull_from { Link Here
1163
1170
1164
    my $today = dt_from_string();
1171
    my $today = dt_from_string();
1165
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
1172
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
1166
        @branches_to_use = grep { !Koha::Calendar->new( branchcode => $_ )->is_holiday($today) } @branches_to_use;
1173
        @branches_to_use = grep { !Koha::DiscreteCalendar->new({ branchcode => $_ })->is_holiday($today) } @branches_to_use;
1167
    }
1174
    }
1168
1175
1169
    return \@branches_to_use;
1176
    return \@branches_to_use;
(-)a/C4/Overdues.pm (-2 / +2 lines)
Lines 348-354 sub get_chargeable_units { Link Here
348
    my $charge_duration;
348
    my $charge_duration;
349
    if ( $unit eq 'hours' ) {
349
    if ( $unit eq 'hours' ) {
350
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
350
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
351
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
351
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
352
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
352
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
353
        } else {
353
        } else {
354
            $charge_duration = $date_returned->delta_ms($date_due);
354
            $charge_duration = $date_returned->delta_ms($date_due);
Lines 359-365 sub get_chargeable_units { Link Here
359
        return $charge_duration->in_units('hours');
359
        return $charge_duration->in_units('hours');
360
    } else {    # days
360
    } else {    # days
361
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
361
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
362
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
362
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
363
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
363
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
364
        } else {
364
        } else {
365
            $charge_duration = $date_returned->delta_days($date_due);
365
            $charge_duration = $date_returned->delta_days($date_due);
(-)a/C4/Reserves.pm (-2 / +2 lines)
Lines 76-86 use C4::Members; Link Here
76
use Koha::Account::Lines;
76
use Koha::Account::Lines;
77
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
77
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
78
use Koha::Biblios;
78
use Koha::Biblios;
79
use Koha::Calendar;
80
use Koha::Cache::Memory::Lite;
79
use Koha::Cache::Memory::Lite;
81
use Koha::CirculationRules;
80
use Koha::CirculationRules;
82
use Koha::Database;
81
use Koha::Database;
83
use Koha::DateUtils qw( dt_from_string output_pref );
82
use Koha::DateUtils qw( dt_from_string output_pref );
83
use Koha::DiscreteCalendar;
84
use Koha::Holds;
84
use Koha::Holds;
85
use Koha::HoldGroup;
85
use Koha::HoldGroup;
86
use Koha::ItemTypes;
86
use Koha::ItemTypes;
Lines 1012-1018 sub CancelExpiredReserves { Link Here
1012
        my $cache_key = sprintf "Calendar_CancelExpiredReserves:%s", $hold->branchcode;
1012
        my $cache_key = sprintf "Calendar_CancelExpiredReserves:%s", $hold->branchcode;
1013
        my $calendar  = $cache->get_from_cache($cache_key);
1013
        my $calendar  = $cache->get_from_cache($cache_key);
1014
        if ( !$calendar ) {
1014
        if ( !$calendar ) {
1015
            $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1015
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
1016
            $cache->set_in_cache( $cache_key, $calendar );
1016
            $cache->set_in_cache( $cache_key, $calendar );
1017
        }
1017
        }
1018
1018
(-)a/Koha/Charges/Fees.pm (-2 / +2 lines)
Lines 21-27 use Modern::Perl; Link Here
21
21
22
use Carp;
22
use Carp;
23
23
24
use Koha::Calendar;
24
use Koha::DiscreteCalendar;
25
use Koha::DateUtils qw( dt_from_string );
25
use Koha::DateUtils qw( dt_from_string );
26
use Koha::Exceptions;
26
use Koha::Exceptions;
27
27
Lines 108-114 sub accumulate_rentalcharge { Link Here
108
    return 0 unless $rentalcharge_increment && $rentalcharge_increment > 0;
108
    return 0 unless $rentalcharge_increment && $rentalcharge_increment > 0;
109
109
110
    my $duration;
110
    my $duration;
111
    my $calendar = Koha::Calendar->new( branchcode => $self->library->id );
111
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->library->id });
112
112
113
    if ( $units eq 'hours' ) {
113
    if ( $units eq 'hours' ) {
114
        if ( $itemtype->rentalcharge_hourly_calendar ) {
114
        if ( $itemtype->rentalcharge_hourly_calendar ) {
(-)a/Koha/Checkouts.pm (-1 / +3 lines)
Lines 55-63 sub calculate_dropbox_date { Link Here
55
            branchcode   => $branchcode,
55
            branchcode   => $branchcode,
56
        }
56
        }
57
    );
57
    );
58
    my $calendar     = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
58
    my $calendar     = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
59
    my $today        = dt_from_string;
59
    my $today        = dt_from_string;
60
    my $dropbox_date = $calendar->addDuration( $today, -1 );
60
    my $dropbox_date = $calendar->addDuration( $today, -1 );
61
    my $dateInfo = $calendar->get_date_info($dropbox_date);
62
    $dropbox_date = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
61
63
62
    return $dropbox_date;
64
    return $dropbox_date;
63
}
65
}
(-)a/Koha/CurbsidePickup.pm (-2 / +2 lines)
Lines 26-32 use base qw(Koha::Object); Link Here
26
use C4::Circulation        qw( CanBookBeIssued AddIssue );
26
use C4::Circulation        qw( CanBookBeIssued AddIssue );
27
use C4::Members::Messaging qw( GetMessagingPreferences );
27
use C4::Members::Messaging qw( GetMessagingPreferences );
28
use C4::Letters            qw( GetPreparedLetter EnqueueLetter );
28
use C4::Letters            qw( GetPreparedLetter EnqueueLetter );
29
use Koha::Calendar;
29
use Koha::DiscreteCalendar;
30
use Koha::DateUtils qw( dt_from_string );
30
use Koha::DateUtils qw( dt_from_string );
31
use Koha::Patron;
31
use Koha::Patron;
32
use Koha::Library;
32
use Koha::Library;
Lines 56-62 sub new { Link Here
56
    Koha::Exceptions::CurbsidePickup::NotEnabled->throw
56
    Koha::Exceptions::CurbsidePickup::NotEnabled->throw
57
        unless $policy && $policy->enabled;
57
        unless $policy && $policy->enabled;
58
58
59
    my $calendar = Koha::Calendar->new( branchcode => $params->{branchcode} );
59
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $params->{branchcode} });
60
    Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw
60
    Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw
61
        if $calendar->is_holiday( $params->{scheduled_pickup_datetime} );
61
        if $calendar->is_holiday( $params->{scheduled_pickup_datetime} );
62
62
(-)a/Koha/DiscreteCalendar.pm (+1399 lines)
Line 0 Link Here
1
package Koha::DiscreteCalendar;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG
19
use Modern::Perl;
20
21
use CGI qw ( -utf8 );
22
use Carp;
23
use DateTime;
24
use DateTime::Format::Strptime;
25
use Data::Dumper;
26
27
use C4::Context;
28
use C4::Output;
29
use Koha::Database;
30
use Koha::DateUtils qw ( dt_from_string output_pref );
31
32
# Global variables to make code more readable
33
our $HOLIDAYS = {
34
    EXCEPTION => 'E',
35
    REPEATABLE => 'R',
36
    SINGLE => 'S',
37
    NEED_VALIDATION => 'N',
38
    FLOAT => 'F',
39
    WEEKLY => 'W',
40
    NONE => 'none'
41
};
42
43
=head1 NAME
44
45
Koha::DiscreteCalendar - Object containing a branches calendar, working with the SQL database
46
47
=head1 SYNOPSIS
48
49
  use Koha::DiscreteCalendar
50
51
  my $c = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' });
52
  my $dt = dt_from_string();
53
54
  # are we open
55
  $open = $c->is_holiday($dt);
56
  # when will item be due if loan period = $dur (a DateTime::Duration object)
57
  $duedate = $c->addDuration($dt, $dur, 'days');
58
59
60
=head1 DESCRIPTION
61
62
  Implements a new Calendar object, but uses the SQL database to keep track of days and holidays.
63
  This results in a performance gain since the optimization is done by the MySQL database/team.
64
65
=head1 METHODS
66
67
=head2 new : Create a (discrete) calendar object
68
69
my $calendar = Koha::DiscreteCalendar->new({ branchcode => 'MAIN' });
70
71
The option branchcode is required
72
73
=cut
74
75
sub new {
76
    my ( $classname, $options ) = @_;
77
    my $self = {};
78
    bless $self, $classname;
79
    for my $o_name ( keys %{ $options } ) {
80
        my $o = lc $o_name;
81
        $self->{$o} = $options->{$o_name};
82
    }
83
    if ( !defined $self->{branchcode} ) {
84
        croak 'No branchcode argument passed to Koha::DiscreteCalendar->new';
85
    }
86
    if ( ref $self->{branchcode} eq 'Koha::Library' ) {
87
        $self->{branchcode} = $self->{branchcode}->branchcode;
88
    }
89
    $self->_init();
90
91
    return $self;
92
}
93
94
sub _init {
95
    my $self = shift;
96
    $self->{days_mode} ||= C4::Context->preference('useDaysMode');
97
    #If the branchcode doesn't exist we use the default calendar.
98
    my $schema = Koha::Database->new->schema;
99
    my $branchcode = $self->{branchcode};
100
    my $dtf = $schema->storage->datetime_parser;
101
    my $today = $dtf->format_datetime(DateTime->today);
102
    my $rs = $schema->resultset('DiscreteCalendar')->single(
103
        {
104
            branchcode => $branchcode,
105
            date       => $today
106
        }
107
    );
108
    #use default if no calendar is found
109
    if (!$rs) {
110
        $self->{branchcode} = undef;
111
        $self->{no_branch_selected} = 1;
112
    }
113
114
}
115
116
=head2 get_dates_info
117
118
  my @dates = $calendar->get_dates_info();
119
120
Returns an array of hashes representing the dates in this calendar. The hash
121
contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, C<$open_hour>,
122
C<$close_hour> and C<$note>.
123
124
=cut
125
126
sub get_dates_info {
127
    my $self = shift;
128
    my $branchcode = $self->{branchcode};
129
    my @datesInfos =();
130
    my $schema = Koha::Database->new->schema;
131
132
    my $rs = $schema->resultset('DiscreteCalendar')->search(
133
        {
134
            branchcode => $branchcode
135
        },
136
        {
137
            select  => [ 'date', { DATE => 'date' } ],
138
            as      => [qw/ date date /],
139
            columns =>[ qw/ holiday_type open_hour close_hour note description/]
140
        },
141
    );
142
143
    while (my $date = $rs->next()) {
144
        my $outputdate = dt_from_string( $date->date(), 'iso');
145
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
146
        push @datesInfos, {
147
            date         => $date->date(),
148
            outputdate   => $outputdate,
149
            holiday_type => $date->holiday_type() ,
150
            open_hour    => $date->open_hour(),
151
            close_hour   => $date->close_hour(),
152
            note         => $date->note(),
153
            description  => $date->description(),
154
        };
155
    }
156
157
    return @datesInfos;
158
}
159
160
=head2 add_new_branch
161
162
    Koha::DiscreteCalendar->add_new_branch($copyBranch, $newBranch)
163
164
This method will copy everything from a given branch to a new branch
165
C<$copyBranch> is the branch to copy from
166
C<$newBranch> is the branch to be created, and copy into
167
168
=cut
169
170
sub add_new_branch {
171
    my ( $classname, $copyBranch, $newBranch) = @_;
172
173
    my $schema = Koha::Database->new->schema;
174
175
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
176
            branchcode => $copyBranch
177
    });
178
179
    unless ($branch_rs->count) {
180
        $copyBranch = $schema->resultset('DiscreteCalendar')->next->branchcode;
181
        $branch_rs = $schema->resultset('DiscreteCalendar')->search({
182
            branchcode => $copyBranch
183
        });
184
    }
185
186
    $schema->{AutoCommit} = 0;
187
    $schema->storage->txn_begin;
188
189
    while (my $row = $branch_rs->next()) {
190
        $schema->resultset('DiscreteCalendar')->create({
191
            date         => $row->date(),
192
            branchcode   => $newBranch,
193
            is_opened    => $row->is_opened(),
194
            holiday_type => $row->holiday_type(),
195
            open_hour    => $row->open_hour(),
196
            close_hour   => $row->close_hour(),
197
        });
198
    }
199
200
    eval { $schema->storage->txn_commit; };
201
202
    if ($@) {
203
        $schema->storage->rollback;
204
    }
205
206
    $schema->{AutoCommit} = 1;
207
}
208
209
=head2 delete_branch
210
211
    Koha::DiscreteCalendar->delete_branch($branchcode)
212
213
This method will delete every discrete_calendar entry for a given branch
214
C<$branchcode> is the code of the branch we want to remove from the table
215
216
=cut
217
218
sub delete_branch {
219
    my ( $classname, $branchcode ) = @_;
220
221
    my $schema = Koha::Database->new->schema;
222
223
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
224
            branchcode => $branchcode
225
    });
226
227
    if ($branch_rs->count) {
228
        $branch_rs->delete;
229
    }
230
}
231
232
233
=head2 get_date_info
234
235
    my $date = $calendar->get_date_info;
236
237
Returns a reference-to-hash representing a DiscreteCalendar date data object.
238
The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>,
239
C<$open_hour>, C<$close_hour> and C<$note>.
240
241
=cut
242
243
sub get_date_info {
244
    my ($self, $date) = @_;
245
    my $branchcode = $self->{branchcode};
246
    my $schema = Koha::Database->new->schema;
247
    my $dtf = $schema->storage->datetime_parser;
248
    #String dates for Database usage
249
    my $date_string = $dtf->format_datetime($date);
250
251
    my $rs = $schema->resultset('DiscreteCalendar')->search(
252
        {
253
            branchcode => $branchcode,
254
        },
255
        {
256
            select  => [ 'date', { DATE => 'date' } ],
257
            as      => [qw/ date date /],
258
            where   => \['DATE(?) = date', $date_string ],
259
            columns =>[ qw/ branchcode holiday_type open_hour close_hour note description/]
260
        },
261
    );
262
    my $dateDTO;
263
    while (my $date = $rs->next()) {
264
        $dateDTO = {
265
            date         => $date->date(),
266
            branchcode   => $date->branchcode(),
267
            holiday_type => $date->holiday_type() ,
268
            open_hour    => $date->open_hour(),
269
            close_hour   => $date->close_hour(),
270
            note         => $date->note(),
271
            description  => $date->description(),
272
        };
273
    }
274
275
    return $dateDTO;
276
}
277
278
=head2 get_max_date
279
280
    my $maxDate = $calendar->get_max_date();
281
282
Returns the furthest date available in the database of current branch.
283
284
=cut
285
286
sub get_max_date {
287
    my $self = shift;
288
    my $branchcode = $self->{branchcode};
289
    my $schema = Koha::Database->new->schema;
290
291
    my $rs = $schema->resultset('DiscreteCalendar')->search(
292
        {
293
            branchcode => $branchcode
294
        },
295
        {
296
            select => [{ MAX => 'date' } ],
297
            as     => [qw/ max /],
298
        }
299
    );
300
301
    return $rs->next()->get_column('max');
302
}
303
304
=head2 get_min_date
305
306
    my $minDate = $calendar->get_min_date();
307
308
Returns the oldest date available in the database of current branch.
309
310
=cut
311
312
sub get_min_date {
313
    my $self = shift;
314
    my $branchcode = $self->{branchcode};
315
    my $schema = Koha::Database->new->schema;
316
317
    my $rs = $schema->resultset('DiscreteCalendar')->search(
318
        {
319
            branchcode => $branchcode
320
        },
321
        {
322
            select => [{ MIN => 'date' } ],
323
            as     => [qw/ min /],
324
        }
325
    );
326
327
    return $rs->next()->get_column('min');
328
}
329
330
=head2 get_unique_holidays
331
332
  my @unique_holidays = $calendar->get_unique_holidays();
333
334
Returns an array of all the date objects that are unique holidays.
335
336
=cut
337
338
sub get_unique_holidays {
339
    my $self = shift;
340
    my $exclude_past = shift // 1;
341
    my $branchcode = $self->{branchcode};
342
    my @unique_holidays;
343
    my $schema = Koha::Database->new->schema;
344
345
    my $rs = $schema->resultset('DiscreteCalendar')->search(
346
        {
347
            branchcode   => $branchcode,
348
            holiday_type => $HOLIDAYS->{EXCEPTION}
349
        },
350
        {
351
            select => [{ DATE => 'date' }, 'note', 'description' ],
352
            as     => [qw/ date note description/],
353
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
354
        }
355
    );
356
    while (my $date = $rs->next()) {
357
        my $outputdate = dt_from_string($date->date(), 'iso');
358
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
359
        push @unique_holidays, {
360
            date       => $date->date(),
361
            outputdate => $outputdate,
362
            note       => $date->note(),
363
            description => $date->description(),
364
        }
365
    }
366
367
    return @unique_holidays;
368
}
369
370
=head2 get_float_holidays
371
372
  my @float_holidays = $calendar->get_float_holidays();
373
374
Returns an array of all the date objects that are float holidays.
375
376
=cut
377
378
sub get_float_holidays {
379
    my $self = shift;
380
    my $exclude_past = shift // 1;
381
    my $branchcode = $self->{branchcode};
382
    my @float_holidays;
383
    my $schema = Koha::Database->new->schema;
384
385
    my $rs = $schema->resultset('DiscreteCalendar')->search(
386
        {
387
            branchcode   => $branchcode,
388
            holiday_type => $HOLIDAYS->{FLOAT}
389
        },
390
        {
391
            select => [{ DATE => 'date' }, 'note', 'description' ],
392
            as     => [qw/ date note description/],
393
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
394
        }
395
    );
396
    while (my $date = $rs->next()) {
397
        my $outputdate = dt_from_string($date->date(), 'iso');
398
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
399
        push @float_holidays, {
400
            date        => $date->date(),
401
            outputdate  => $outputdate,
402
            note        => $date->note(),
403
            description => $date->description(),
404
        }
405
    }
406
407
    return @float_holidays;
408
}
409
410
=head2 get_need_validation_holidays
411
412
  my @need_validation_holidays = $calendar->get_need_validation_holidays();
413
414
Returns an array of all the date objects that are float holidays in need of validation.
415
416
=cut
417
418
sub get_need_validation_holidays {
419
    my $self = shift;
420
    my $exclude_past = shift // 1;
421
    my $branchcode = $self->{branchcode};
422
    my @need_validation_holidays;
423
    my $schema = Koha::Database->new->schema;
424
425
    my $rs = $schema->resultset('DiscreteCalendar')->search(
426
        {
427
            branchcode   => $branchcode,
428
            holiday_type => $HOLIDAYS->{NEED_VALIDATION}
429
        },
430
        {
431
            select => [{ DATE => 'date' }, 'note', 'description' ],
432
            as     => [qw/ date note description/],
433
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
434
        }
435
    );
436
    while (my $date = $rs->next()) {
437
        my $outputdate = dt_from_string($date->date(), 'iso');
438
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
439
        push @need_validation_holidays, {
440
            date        => $date->date(),
441
            outputdate  => $outputdate,
442
            note        => $date->note(),
443
            description => $date->description(),
444
        }
445
    }
446
447
    return @need_validation_holidays;
448
}
449
450
=head2 get_repeatable_holidays
451
452
  my @repeatable_holidays = $calendar->get_repeatable_holidays();
453
454
Returns an array of all the date objects that are repeatable holidays.
455
456
=cut
457
458
sub get_repeatable_holidays {
459
    my $self = shift;
460
    my $exclude_past = shift // 1;
461
    my $branchcode = $self->{branchcode};
462
    my @repeatable_holidays;
463
    my $schema = Koha::Database->new->schema;
464
465
    my $rs = $schema->resultset('DiscreteCalendar')->search(
466
        {
467
            branchcode   => $branchcode,
468
            holiday_type => $HOLIDAYS->{'REPEATABLE'},
469
470
        },
471
        {
472
            select => \[ 'distinct DAY(date), MONTH(date), note, description'],
473
            as     => [qw/ day month note description/],
474
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
475
        }
476
    );
477
478
    while (my $date = $rs->next()) {
479
        push @repeatable_holidays, {
480
            day => $date->get_column('day'),
481
            month => $date->get_column('month'),
482
            note => $date->note(),
483
            description => $date->description(),
484
        };
485
    }
486
487
    return @repeatable_holidays;
488
}
489
490
=head2 get_week_days_holidays
491
492
  my @week_days_holidays = $calendar->get_week_days_holidays;
493
494
Returns an array of all the date objects that are weekly holidays.
495
496
=cut
497
498
sub get_week_days_holidays {
499
    my $self = shift;
500
    my $exclude_past = shift // 1;
501
    my $branchcode = $self->{branchcode};
502
    my @week_days;
503
    my $schema = Koha::Database->new->schema;
504
505
    my $rs = $schema->resultset('DiscreteCalendar')->search(
506
        {
507
            holiday_type => $HOLIDAYS->{WEEKLY},
508
            branchcode   => $branchcode,
509
        },
510
        {
511
            select   => \[ 'distinct DAYOFWEEK(date), note, description'],
512
            as       => [qw/ weekday note description /],
513
            where    => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
514
        }
515
    );
516
517
    while (my $date = $rs->next()) {
518
        push @week_days, {
519
            weekday => $date->get_column('weekday'),
520
            note    => $date->note(),
521
            description => $date->description(),
522
        };
523
    }
524
525
    return @week_days;
526
}
527
528
=head2 edit_holiday
529
530
Modifies a date or a range of dates
531
532
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
533
534
C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range.
535
536
C<$holiday_type> Is the type of the holiday :
537
    E : Exception holiday, single day.
538
    F : Floating holiday, different day each year.
539
    N : Needs validation, copied float holiday from the past
540
    R : Repeatable holiday, repeated on same date.
541
    W : Weekly holiday, same day of the week.
542
543
C<$open_hour> Is the opening hour.
544
C<$close_hour> Is the closing hour.
545
C<$start_date> Is the start of the range of dates.
546
C<$end_date> Is the end of the range of dates.
547
C<$delete_type> Delete all
548
C<$today> Today based on the local date, using JavaScript.
549
550
=cut
551
552
sub edit_holiday {
553
    my $self = shift;
554
    my ($params) = @_;
555
556
    my $title        = $params->{title};
557
    my $description  = $params->{description};
558
    my $weekday      = $params->{weekday} || '';
559
    my $holiday_type = $params->{holiday_type};
560
561
    my $start_date   = $params->{start_date};
562
    my $end_date     = $params->{end_date};
563
564
    my $open_hour    = $params->{open_hour} || '';
565
    my $close_hour   = $params->{close_hour} || '';
566
567
    my $delete_type  = $params->{delete_type} || undef;
568
    my $all_branches = $params->{all_branches} // 0;
569
    my $today        = $params->{today} || dt_from_string()->truncate( to => 'day' );
570
571
    # When override param is set, this function will allow past dates to be set as holidays,
572
    # otherwise it will not. This is meant to only be used for testing.
573
    my $override = $params->{override} || 0;
574
575
    my $schema = Koha::Database->new->schema;
576
    $schema->{AutoCommit} = 0;
577
    $schema->storage->txn_begin;
578
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
579
580
    #String dates for Database usage
581
    my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
582
    my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
583
    $today = $dtf->format_datetime($today->clone->truncate(to => 'day'));
584
    my %updateValues = (
585
        is_opened    => 0,
586
        note         => $title,
587
        description  => $description,
588
        holiday_type => $holiday_type,
589
    );
590
    $updateValues{open_hour} = $open_hour if $open_hour ne '';
591
    $updateValues{close_hour} = $close_hour if $close_hour ne '';
592
593
    my $limits = ( $all_branches ) ? {} : { branchcode => $self->{branchcode} };
594
595
    if ($holiday_type eq $HOLIDAYS->{WEEKLY}) {
596
        #Update weekly holidays
597
        if ($start_date_string eq $end_date_string) {
598
            $end_date_string = $self->get_max_date();
599
        }
600
        my $rs = $schema->resultset('DiscreteCalendar')->search(
601
            $limits,
602
            {
603
                where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string],
604
            }
605
        );
606
607
        while (my $date = $rs->next()) {
608
            $date->update(\%updateValues);
609
        }
610
    } elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) {
611
        #Update Exception Float and Needs Validation holidays
612
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
613
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
614
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
615
        }
616
        $where->{date}{'>='} = $today unless $override;
617
618
        my $rs = $schema->resultset('DiscreteCalendar')->search(
619
            $limits,
620
            {
621
                where => $where,
622
            }
623
        );
624
        while (my $date = $rs->next()) {
625
            $date->update(\%updateValues);
626
        }
627
628
    } elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) {
629
        #Update repeatable holidays
630
        my $parser = DateTime::Format::Strptime->new(
631
           pattern  => '%m-%d',
632
           on_error => 'croak',
633
        );
634
        #Format the dates to have only month-day ex: 01-04 for January 4th
635
        $start_date = $parser->format_datetime($start_date);
636
        $end_date = $parser->format_datetime($end_date);
637
        my $where = { -and => [ \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] };
638
        push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override;
639
        my $rs = $schema->resultset('DiscreteCalendar')->search(
640
            $limits,
641
            {
642
                where => $where,
643
            }
644
        );
645
        while (my $date = $rs->next()) {
646
            $date->update(\%updateValues);
647
        }
648
649
    } else {
650
        #Update date(s)/Remove holidays
651
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
652
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
653
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
654
        }
655
        $where->{date}{'>='} = $today unless $override;
656
657
        my $rs = $schema->resultset('DiscreteCalendar')->search(
658
            $limits,
659
            {
660
                where => $where,
661
            }
662
        );
663
        #If none, the date(s) will be normal days, else,
664
        if ($holiday_type eq 'none') {
665
            $updateValues{holiday_type} ='';
666
            $updateValues{is_opened} =1;
667
        } else {
668
            delete $updateValues{holiday_type};
669
            delete $updateValues{is_opened};
670
        }
671
672
        while (my $date = $rs->next()) {
673
            if ($delete_type) {
674
                if ($date->holiday_type() eq $HOLIDAYS->{WEEKLY}) {
675
                    $self->remove_weekly_holidays($weekday, \%updateValues, $today, $date->branchcode);
676
                } elsif ($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}) {
677
                    $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today, $date->branchcode);
678
                }
679
            } else {
680
                $date->update(\%updateValues);
681
            }
682
        }
683
    }
684
    $schema->storage->txn_commit;
685
    $schema->{AutoCommit} = 1;
686
}
687
688
=head2 remove_weekly_holidays
689
690
    $calendar->remove_weekly_holidays($weekday, $updateValues, $today, $branchcode);
691
692
Removes a weekly holiday and updates the days' parameters
693
C<$weekday> is the weekday to un-holiday
694
C<$updatevalues> is hashref containing the new parameters
695
C<$today> is today's date
696
C<$branchcode> is the branchcode of the library
697
698
=cut
699
700
sub remove_weekly_holidays {
701
    my ($self, $weekday, $updateValues, $today, $branchcode) = @_;
702
    my $schema = Koha::Database->new->schema;
703
704
    my $rs = $schema->resultset('DiscreteCalendar')->search(
705
        {
706
            branchcode   => $branchcode,
707
            is_opened    => 0,
708
            holiday_type => $HOLIDAYS->{WEEKLY}
709
        },
710
        {
711
            where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]},
712
        }
713
    );
714
715
    while (my $date = $rs->next()) {
716
        $date->update($updateValues);
717
    }
718
}
719
720
=head2 remove_repeatable_holidays
721
722
    $calendar->remove_repeatable_holidays($startDate, $endDate, $today, $branchcode);
723
724
Removes a repeatable holiday and updates the days' parameters
725
C<$startDatey> is the start date of the repeatable holiday
726
C<$endDate> is the end date of the repeatble holiday
727
C<$updatevalues> is hashref containing the new parameters
728
C<$today> is today's date
729
C<$branchcode> is the branchcode of the library
730
731
=cut
732
733
sub remove_repeatable_holidays {
734
    my ($self, $startDate, $endDate, $updateValues, $today, $branchcode) = @_;
735
    my $schema = Koha::Database->new->schema;
736
    my $parser = DateTime::Format::Strptime->new(
737
        pattern  => '%m-%d',
738
        on_error => 'croak',
739
    );
740
    #Format the dates to have only month-day ex: 01-04 for January 4th
741
    $startDate = $parser->format_datetime($startDate);
742
    $endDate = $parser->format_datetime($endDate);
743
744
    my $rs = $schema->resultset('DiscreteCalendar')->search(
745
        {
746
            branchcode   => $branchcode,
747
            is_opened    => 0,
748
            holiday_type => $HOLIDAYS->{REPEATABLE},
749
        },
750
        {
751
            where => { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]},
752
        }
753
    );
754
755
    while (my $date = $rs->next()) {
756
        $date->update($updateValues);
757
    }
758
}
759
760
=head2 copy_to_branch
761
762
  $calendar->copy_to_branch($branch2);
763
764
Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self>
765
but not in C<$branch2>
766
767
C<$branch2> the branch to copy into
768
769
=cut
770
771
sub copy_to_branch {
772
    my ($self, $newBranch) =@_;
773
    my $branchcode = $self->{branchcode};
774
    my $schema = Koha::Database->new->schema;
775
776
    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
777
        {
778
            branchcode => $branchcode
779
        },
780
        {
781
            columns => [ qw/ date is_opened note holiday_type open_hour close_hour /]
782
        }
783
    );
784
    while (my $copyDate = $copyFrom->next()) {
785
        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
786
            {
787
                branchcode => $newBranch,
788
                date       => $copyDate->date(),
789
            },
790
            {
791
                columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /]
792
            }
793
        );
794
        #if the date does not exist in the copyTO branch, than skip it.
795
        if ($copyTo->count ==0) {
796
            next;
797
        }
798
        $copyTo->next()->update({
799
            is_opened    => $copyDate->is_opened(),
800
            holiday_type => $copyDate->holiday_type(),
801
            note         => $copyDate->note(),
802
            open_hour    => $copyDate->open_hour(),
803
            close_hour   => $copyDate->close_hour()
804
        });
805
    }
806
}
807
808
=head2 is_opened
809
810
    $calendar->is_opened($date)
811
812
Returns whether the library is open on C<$date>
813
814
=cut
815
816
sub is_opened {
817
    my($self, $date) = @_;
818
    my $branchcode = $self->{branchcode};
819
    my $schema = Koha::Database->new->schema;
820
    my $dtf = $schema->storage->datetime_parser;
821
    $date= $dtf->format_datetime($date);
822
    #if the date is not found
823
    my $is_opened = -1;
824
    my $rs = $schema->resultset('DiscreteCalendar')->search(
825
        {
826
            branchcode => $branchcode,
827
        },
828
        {
829
            where => \['date = DATE(?)', $date]
830
        }
831
    );
832
    $is_opened = $rs->next()->is_opened() if $rs->count() != 0;
833
834
    return $is_opened;
835
}
836
837
=head2 is_holiday
838
839
    $calendar->is_holiday($date)
840
841
Returns whether C<$date> is a holiday or not
842
843
=cut
844
845
sub is_holiday {
846
    my($self, $date) = @_;
847
    my $branchcode = $self->{branchcode};
848
    my $schema = Koha::Database->new->schema;
849
    my $dtf = $schema->storage->datetime_parser;
850
    $date= $dtf->format_datetime($date);
851
    #if the date is not found
852
    my $isHoliday = -1;
853
    my $rs = $schema->resultset('DiscreteCalendar')->search(
854
        {
855
            branchcode => $branchcode,
856
        },
857
        {
858
            where => \['date = DATE(?)', $date]
859
        }
860
    );
861
862
    if ($rs->count() != 0) {
863
        $isHoliday = ($rs->first()->is_opened() ? 0 : 1);
864
    }
865
866
    return $isHoliday;
867
}
868
869
=head2 copy_holiday
870
871
  $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
872
873
Copies a holiday's parameters from a range to a new range
874
C<$from_startDate> the source holiday's start date
875
C<$from_endDate> the source holiday's end date
876
C<$to_startDate> the destination holiday's start date
877
C<$to_endDate> the destination holiday's end date
878
C<$daysnumber> the number of days in the range.
879
880
Both ranges should have the same number of days in them.
881
882
=cut
883
884
sub copy_holiday {
885
    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
886
    my $branchcode = $self->{branchcode};
887
    my $copyFromType = $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
888
    my $schema = Koha::Database->new->schema;
889
    my $dtf = $schema->storage->datetime_parser;
890
891
    if ($copyFromType eq 'oneDay') {
892
        my $where;
893
        $to_startDate = $dtf->format_datetime($to_startDate);
894
        if ($to_startDate && $to_endDate) {
895
            $to_endDate = $dtf->format_datetime($to_endDate);
896
            $where = { date => { -between => [$to_startDate, $to_endDate]}};
897
        } else {
898
            $where = { date => $to_startDate };
899
        }
900
901
        $from_startDate = $dtf->format_datetime($from_startDate);
902
        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
903
            {
904
                branchcode => $branchcode,
905
                date       => $from_startDate
906
            }
907
        );
908
        my $toDates = $schema->resultset('DiscreteCalendar')->search(
909
            {
910
                branchcode => $branchcode,
911
            },
912
            {
913
                where => $where
914
            }
915
        );
916
917
        my $copyDate = $fromDate->next();
918
        while (my $date = $toDates->next()) {
919
            $date->update({
920
                is_opened    => $copyDate->is_opened(),
921
                holiday_type => $copyDate->holiday_type(),
922
                note         => $copyDate->note(),
923
                open_hour    => $copyDate->open_hour(),
924
                close_hour   => $copyDate->close_hour()
925
            })
926
        }
927
928
    } else {
929
        my $endDate = dt_from_string($from_endDate);
930
        $to_startDate = $dtf->format_datetime($to_startDate);
931
        $to_endDate = $dtf->format_datetime($to_endDate);
932
        if ($daysnumber == 7) {
933
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
934
                my $formatedDate = $dtf->format_datetime($tempDate);
935
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
936
                    {
937
                        branchcode => $branchcode,
938
                        date       => $formatedDate,
939
                    },
940
                    {
941
                        select  => [{ DAYOFWEEK => 'date' }],
942
                        as      => [qw/ weekday /],
943
                        columns =>[ qw/ holiday_type note open_hour close_hour note description/]
944
                    }
945
                );
946
                my $copyDate = $fromDate->next();
947
                my $weekday = $copyDate->get_column('weekday');
948
949
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
950
                    {
951
                        branchcode => $branchcode,
952
953
                    },
954
                    {
955
                        where => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday},
956
                    }
957
                );
958
                my $copyToDate = $toDate->next();
959
                $copyToDate->update({
960
                    is_opened    => $copyDate->is_opened(),
961
                    holiday_type => $copyDate->holiday_type(),
962
                    note         => $copyDate->note(),
963
                    open_hour    => $copyDate->open_hour(),
964
                    close_hour   => $copyDate->close_hour()
965
                });
966
967
            }
968
        } else {
969
            my $to_startDate = dt_from_string($to_startDate);
970
            my $to_endDate = dt_from_string($to_endDate);
971
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
972
                my $from_formatedDate = $dtf->format_datetime($tempDate);
973
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
974
                    {
975
                        branchcode => $branchcode,
976
                        date       => $from_formatedDate,
977
                    },
978
                    {
979
                        order_by => { -asc => 'date' }
980
                    }
981
                );
982
                my $to_formatedDate = $dtf->format_datetime($to_startDate);
983
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
984
                    {
985
                        branchcode => $branchcode,
986
                        date       => $to_formatedDate
987
                    },
988
                    {
989
                        order_by => { -asc => 'date' }
990
                    }
991
                );
992
                my $copyDate = $fromDate->next();
993
                $toDate->next()->update({
994
                    is_opened    => $copyDate->is_opened(),
995
                    holiday_type => $copyDate->holiday_type(),
996
                    note         => $copyDate->note(),
997
                    open_hour    => $copyDate->open_hour(),
998
                    close_hour   => $copyDate->close_hour()
999
                });
1000
                $to_startDate->add(days =>1);
1001
            }
1002
        }
1003
1004
1005
    }
1006
}
1007
1008
=head2 days_between
1009
1010
   $cal->days_between( $start_date, $end_date )
1011
1012
Calculates the number of days the library is opened between C<$start_date> and C<$end_date>
1013
1014
=cut
1015
1016
sub days_between {
1017
    my ($self, $start_date, $end_date, ) = @_;
1018
    my $branchcode = $self->{branchcode};
1019
1020
    if ( $start_date->compare($end_date) > 0 ) {
1021
        # swap dates
1022
        ($start_date, $end_date) = ($end_date, $start_date);
1023
    }
1024
1025
    my $schema = Koha::Database->new->schema;
1026
    my $dtf = $schema->storage->datetime_parser;
1027
    $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
1028
    $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
1029
1030
    my $days_between = $schema->resultset('DiscreteCalendar')->search(
1031
        {
1032
            branchcode => $branchcode,
1033
            is_opened  => 1,
1034
        },
1035
        {
1036
            where => \['date >= date(?) AND date < date(?)', $start_date, $end_date]
1037
        }
1038
    );
1039
1040
    return DateTime::Duration->new( days => $days_between->count());
1041
}
1042
1043
=head2 next_open_day
1044
1045
   $open_date = $self->next_open_day($base_date);
1046
1047
Returns a string representing the next day the library is open, starting from C<$base_date>
1048
1049
=cut
1050
1051
sub next_open_day {
1052
    my ( $self, $date, $dayweek ) = @_;
1053
    my $branchcode = $self->{branchcode};
1054
    my $schema = Koha::Database->new->schema;
1055
    my $dtf = $schema->storage->datetime_parser;
1056
    my $formatted_date = $dtf->format_datetime($date);
1057
1058
    my $options = {
1059
        order_by => { -asc => 'date' },
1060
        rows     => 1,
1061
    };
1062
    $options->{where} = \['DAYOFWEEK(date) = DAYOFWEEK(?)', $formatted_date] if $dayweek;
1063
1064
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1065
        {
1066
            branchcode => $branchcode,
1067
            is_opened  => 1,
1068
            date => { '>' => \['DATE(?)', $formatted_date] },
1069
        },
1070
        $options
1071
    );
1072
1073
    my $next = $rs->next();
1074
    unless ( $next ) {
1075
        warn "No next opened date in calendar. Reduced to current date: $formatted_date.";
1076
        return $date;
1077
    }
1078
    return dt_from_string( $next->date(), 'iso');
1079
}
1080
1081
=head2 prev_open_day
1082
1083
   $open_date = $self->prev_open_day($base_date);
1084
1085
Returns a string representing the closest previous day the library was open, starting from C<$base_date>
1086
1087
=cut
1088
1089
sub prev_open_day {
1090
    my ( $self, $date, $dayweek ) = @_;
1091
    my $branchcode = $self->{branchcode};
1092
    my $schema = Koha::Database->new->schema;
1093
    my $dtf = $schema->storage->datetime_parser;
1094
    my $formatted_date = $dtf->format_datetime($date);
1095
1096
    my $options = {
1097
        order_by => { -desc => 'date' },
1098
        rows     => 1,
1099
    };
1100
    $options->{where} = \['DAYOFWEEK(date) = DAYOFWEEK(?)', $formatted_date] if $dayweek;
1101
1102
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1103
        {
1104
            branchcode => $branchcode,
1105
            is_opened  => 1,
1106
            date => { '<' => \['DATE(?)', $formatted_date] },
1107
        },
1108
        $options
1109
    );
1110
1111
    my $prev = $rs->next();
1112
    unless ( $prev ) {
1113
        warn "No previous opened date in calendar. Reduced to current date: $formatted_date.";
1114
        return $date;
1115
    }
1116
    return dt_from_string( $prev->date(), 'iso');
1117
}
1118
1119
=head2 days_forward
1120
1121
    $fwrd_date = $calendar->days_forward($start, $count)
1122
1123
Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed.
1124
1125
=cut
1126
1127
sub days_forward {
1128
    my $self = shift;
1129
    my $start_dt = shift;
1130
    my $num_days = shift;
1131
1132
    return $start_dt unless $num_days > 0;
1133
1134
    my $base_dt = $start_dt->clone();
1135
1136
    while ($num_days--) {
1137
        $base_dt = $self->next_open_day($base_dt);
1138
    }
1139
1140
    return $base_dt;
1141
}
1142
1143
=head2 hours_between
1144
1145
    $hours = $calendar->hours_between($start_dt, $end_dt)
1146
1147
Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise
1148
version, which simply calculates the number of day times 24. To take opening hours into account
1149
see C<open_hours_between>/
1150
1151
=cut
1152
1153
sub hours_between {
1154
    my ($self, $start_dt, $end_dt) = @_;
1155
    my $branchcode = $self->{branchcode};
1156
    my $schema = Koha::Database->new->schema;
1157
    my $dtf = $schema->storage->datetime_parser;
1158
    my $start_date = $start_dt->clone();
1159
    my $end_date = $end_dt->clone();
1160
    my $duration = $end_date->delta_ms($start_date);
1161
    $start_date->truncate( to => 'day' );
1162
    $end_date->truncate( to => 'day' );
1163
1164
    # NB this is a kludge in that it assumes all days are 24 hours
1165
    # However for hourly loans the logic should be expanded to
1166
    # take into account open/close times then it would be a duration
1167
    # of library open hours
1168
    my $skipped_days = 0;
1169
    $start_date = $dtf->format_datetime($start_date);
1170
    $end_date = $dtf->format_datetime($end_date);
1171
    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
1172
        {
1173
            branchcode => $branchcode,
1174
            is_opened  => 0,
1175
            date => {
1176
                '>=' => $start_date,
1177
                '<' => $end_date,
1178
            },
1179
        },
1180
    );
1181
1182
    if ($skipped_days = $hours_between->count()) {
1183
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
1184
    }
1185
1186
    return $duration;
1187
}
1188
1189
=head2 open_hours_between
1190
1191
  $hours = $calendar->open_hours_between($start_date, $end_date)
1192
1193
Returns the number of hours between C<$start_date> and C<$end_date>, taking into
1194
account the opening hours of the library.
1195
1196
=cut
1197
1198
sub open_hours_between {
1199
    my ($self, $start_date, $end_date) = @_;
1200
    my $branchcode = $self->{branchcode};
1201
    my $schema = Koha::Database->new->schema;
1202
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1203
    $start_date = $dtf->format_datetime($start_date);
1204
    $end_date = $dtf->format_datetime($end_date);
1205
1206
    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
1207
        {
1208
            branchcode => $branchcode,
1209
            is_opened  => 1,
1210
        },
1211
        {
1212
            select => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'],
1213
            as     => [qw /hours_between/],
1214
            where  => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1215
        }
1216
    );
1217
1218
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1219
        {
1220
            branchcode => $branchcode,
1221
        },
1222
        {
1223
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1224
            rows => 1,
1225
        }
1226
    );
1227
1228
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1229
        {
1230
            branchcode => $branchcode,
1231
        },
1232
        {
1233
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1234
            rows => 1,
1235
        }
1236
    );
1237
1238
    #Capture the time portion of the date
1239
    $start_date =~ /\s(.*)/;
1240
    my $loan_date_time = $1;
1241
    $end_date =~ /\s(.*)/;
1242
    my $return_date_time = $1;
1243
1244
    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
1245
        {
1246
            branchcode => $branchcode,
1247
            is_opened  => 1,
1248
        },
1249
        {
1250
            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()],
1251
            as     => [qw /not_used_hours/],
1252
        }
1253
    );
1254
1255
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1256
}
1257
1258
=head2 addDuration
1259
1260
  my $dt = $calendar->addDuration($date, $dur, $unit)
1261
1262
C<$date> is a DateTime object representing the starting date of the interval.
1263
C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy)
1264
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
1265
1266
=cut
1267
1268
sub addDuration {
1269
    my ( $self, $startdate, $add_duration, $unit ) = @_;
1270
1271
    # Default to days duration (legacy support I guess)
1272
    if ( ref $add_duration ne 'DateTime::Duration' ) {
1273
        $add_duration = DateTime::Duration->new( days => $add_duration );
1274
    }
1275
1276
    $unit ||= 'days'; # default days ?
1277
    my $dt;
1278
1279
    if ( $unit eq 'hours' ) {
1280
        # Fixed for legacy support. Should be set as a branch parameter
1281
        my $return_by_hour = 10;
1282
1283
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1284
    } else {
1285
        # days
1286
        $dt = $self->addDays($startdate, $add_duration);
1287
    }
1288
1289
    return $dt;
1290
}
1291
1292
=head2 addHours
1293
1294
  $end = $calendar->addHours($start, $hours_duration, $return_by_hour)
1295
1296
Add C<$hours_duration> to C<$start> date.
1297
C<$return_by_hour> is an integer value representing the opening hour for the branch
1298
1299
=cut
1300
1301
sub addHours {
1302
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
1303
    my $base_date = $startdate->clone();
1304
1305
    $base_date->add_duration($hours_duration);
1306
1307
    # If we are using the calendar behave for now as if Datedue
1308
    # was the chosen option (current intended behaviour)
1309
1310
    if ( $self->{days_mode} ne 'Days' &&
1311
    $self->is_holiday($base_date) ) {
1312
1313
        if ( $hours_duration->is_negative() ) {
1314
            $base_date = $self->prev_open_day($base_date);
1315
        } else {
1316
            $base_date = $self->next_open_day($base_date);
1317
        }
1318
1319
        $base_date->set_hour($return_by_hour);
1320
1321
    }
1322
1323
    return $base_date;
1324
}
1325
1326
=head2 addDays
1327
1328
  $date = $calendar->addDays($start, $duration)
1329
1330
Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set
1331
to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue'
1332
it calculates the date normally, and then pushes to result to the next open day.
1333
1334
=cut
1335
1336
sub addDays {
1337
    my ( $self, $startdate, $days_duration ) = @_;
1338
    my $base_date = $startdate->clone();
1339
1340
    $self->{days_mode} ||= q{};
1341
1342
    if ( $self->{days_mode} eq 'Calendar' ) {
1343
        # use the calendar to skip all days the library is closed
1344
        # when adding
1345
        my $days = abs $days_duration->in_units('days');
1346
1347
        if ( $days_duration->is_negative() ) {
1348
            while ($days) {
1349
                $base_date = $self->prev_open_day($base_date);
1350
                --$days;
1351
            }
1352
        } else {
1353
            while ($days) {
1354
                $base_date = $self->next_open_day($base_date);
1355
                --$days;
1356
            }
1357
        }
1358
1359
    } else { # Days or Datedue
1360
        # use straight days, then use calendar to push
1361
        # the date to the next open day as appropriate
1362
        # if Datedue or Dayweek
1363
        $base_date->add_duration($days_duration);
1364
1365
        if ( $self->{days_mode} eq 'Datedue' ||
1366
            $self->{days_mode} eq 'Dayweek') {
1367
            # Datedue or Dayweek, then use the calendar to push
1368
            # the date to the next open day if holiday
1369
            if ( $self->is_holiday($base_date) ) {
1370
                my $days = $days_duration->in_units('days');
1371
1372
                my $dayweek = 0;
1373
                if (
1374
                    $self->{days_mode} eq 'Dayweek' &&
1375
                    $days % 7 == 0      # Is it a period based on weeks
1376
                ) {
1377
                    my $dow = $base_date->day_of_week;
1378
                    # Representation fix
1379
                    # DateTime object dow (1-7) where Monday is 1
1380
                    # in Koha::DiscreteCalendar 1 = Sunday, not 7.
1381
                    $dow = ( $dow == 7 ) ? 1 : $dow + 1;
1382
1383
                    my @weekly_holidays = $self->get_week_days_holidays();
1384
                    $dayweek = !(@weekly_holidays && grep $_->{weekday} == $dow, @weekly_holidays);
1385
                }
1386
1387
                if ( $days_duration->is_negative() ) {
1388
                    $base_date = $self->prev_open_day($base_date, $dayweek);
1389
                } else {
1390
                    $base_date = $self->next_open_day($base_date, $dayweek);
1391
                }
1392
            }
1393
        }
1394
    }
1395
1396
    return $base_date;
1397
}
1398
1399
1;
(-)a/Koha/Hold.pm (-3 / +3 lines)
Lines 34-40 use Koha::Biblios; Link Here
34
use Koha::Hold::CancellationRequests;
34
use Koha::Hold::CancellationRequests;
35
use Koha::Items;
35
use Koha::Items;
36
use Koha::Libraries;
36
use Koha::Libraries;
37
use Koha::Calendar;
37
use Koha::DiscreteCalendar;
38
use Koha::Plugins;
38
use Koha::Plugins;
39
39
40
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
40
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
Lines 71-77 sub age { Link Here
71
    my $age;
71
    my $age;
72
72
73
    if ($use_calendar) {
73
    if ($use_calendar) {
74
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
74
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
75
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
75
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
76
    } else {
76
    } else {
77
        $age = $today->delta_days( dt_from_string( $self->reservedate ) );
77
        $age = $today->delta_days( dt_from_string( $self->reservedate ) );
Lines 399-405 sub set_waiting { Link Here
399
                branchcode   => $self->branchcode,
399
                branchcode   => $self->branchcode,
400
            }
400
            }
401
        );
401
        );
402
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
402
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode, days_mode => $daysmode });
403
403
404
        $new_expiration_date = $calendar->days_forward( dt_from_string( $self->waitingdate ), $max_pickup_delay );
404
        $new_expiration_date = $calendar->days_forward( dt_from_string( $self->waitingdate ), $max_pickup_delay );
405
    }
405
    }
(-)a/Koha/Patron.pm (-1 / +1 lines)
Lines 1254-1260 sub has_restricting_overdues { Link Here
1254
1254
1255
    my $calendar;
1255
    my $calendar;
1256
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
1256
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
1257
        $calendar = Koha::Calendar->new( branchcode => $params->{issue_branchcode} );
1257
        $calendar = Koha::DiscreteCalendar->new( branchcode => $params->{issue_branchcode} );
1258
    }
1258
    }
1259
1259
1260
    my $dtf    = Koha::Database->new->schema->storage->datetime_parser;
1260
    my $dtf    = Koha::Database->new->schema->storage->datetime_parser;
(-)a/Koha/Schema/Result/DiscreteCalendar.pm (-2 / +10 lines)
Lines 55-60 __PACKAGE__->table("discrete_calendar"); Link Here
55
  is_nullable: 1
55
  is_nullable: 1
56
  size: 30
56
  size: 30
57
57
58
=head2 description
59
60
  data_type: 'mediumtext'
61
  default_value: ''''
62
  is_nullable: 1
63
58
=head2 open_hour
64
=head2 open_hour
59
65
60
  data_type: 'time'
66
  data_type: 'time'
Lines 82-87 __PACKAGE__->add_columns( Link Here
82
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 1 },
88
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 1 },
83
  "note",
89
  "note",
84
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 30 },
90
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 30 },
91
  "description",
92
  { data_type => "mediumtext", default_value => "''", is_nullable => 1 },
85
  "open_hour",
93
  "open_hour",
86
  { data_type => "time", is_nullable => 0 },
94
  { data_type => "time", is_nullable => 0 },
87
  "close_hour",
95
  "close_hour",
Lines 103-110 __PACKAGE__->add_columns( Link Here
103
__PACKAGE__->set_primary_key("branchcode", "date");
111
__PACKAGE__->set_primary_key("branchcode", "date");
104
112
105
113
106
# Created by DBIx::Class::Schema::Loader v0.07045 @ 2017-04-19 10:07:41
114
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-10-20 11:37:37
107
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wtctW8ZzCkyCZFZmmavFEw
115
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:/w9T8ShNcZsKRpeGaePHoA
108
116
109
117
110
# You can replace this text with custom code or comments, and it will be preserved on regeneration
118
# You can replace this text with custom code or comments, and it will be preserved on regeneration
(-)a/admin/branches.pl (+3 lines)
Lines 226-231 if ( $op eq 'add_form' ) { Link Here
226
                    my @additional_fields = $library->prepare_cgi_additional_field_values( $input, 'branches' );
226
                    my @additional_fields = $library->prepare_cgi_additional_field_values( $input, 'branches' );
227
                    $library->set_additional_fields( \@additional_fields );
227
                    $library->set_additional_fields( \@additional_fields );
228
228
229
                    Koha::DiscreteCalendar->add_new_branch(undef, $branchcode);
230
229
                    push @messages, { type => 'message', code => 'success_on_insert' };
231
                    push @messages, { type => 'message', code => 'success_on_insert' };
230
                }
232
                }
231
            );
233
            );
Lines 270-275 if ( $op eq 'add_form' ) { Link Here
270
    if ( $@ or not $deleted ) {
272
    if ( $@ or not $deleted ) {
271
        push @messages, { type => 'alert', code => 'error_on_delete' };
273
        push @messages, { type => 'alert', code => 'error_on_delete' };
272
    } else {
274
    } else {
275
        Koha::DiscreteCalendar->delete_branch($branchcode);
273
        push @messages, { type => 'message', code => 'success_on_delete' };
276
        push @messages, { type => 'message', code => 'success_on_delete' };
274
    }
277
    }
275
    $op = 'list';
278
    $op = 'list';
(-)a/circ/returns.pl (-1 lines)
Lines 45-51 use C4::RotatingCollections; Link Here
45
use Koha::AuthorisedValues;
45
use Koha::AuthorisedValues;
46
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
46
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
47
use Koha::BiblioFrameworks;
47
use Koha::BiblioFrameworks;
48
use Koha::Calendar;
49
use Koha::Checkouts;
48
use Koha::Checkouts;
50
use Koha::CirculationRules;
49
use Koha::CirculationRules;
51
use Koha::DateUtils qw( dt_from_string );
50
use Koha::DateUtils qw( dt_from_string );
(-)a/koha-tmpl/intranet-tmpl/prog/css/src/calendar.scss (+44 lines)
Lines 1-8 Link Here
1
$daySize: 45px;
1
$daySize: 45px;
2
$pastdate_bg: #E6E6E6;
3
$normalday_bg: #F4F8F9;
2
$exception_bg: #B3D4FF;
4
$exception_bg: #B3D4FF;
3
$holiday_bg: #FFAEAE;
5
$holiday_bg: #FFAEAE;
4
$repeatableweekly_bg: #FFFF99;
6
$repeatableweekly_bg: #FFFF99;
5
$repeatableyearly_bg: #FFCC66;
7
$repeatableyearly_bg: #FFCC66;
8
$float_bg: #66FF33;
6
$selected: #B9DB88;
9
$selected: #B9DB88;
7
10
8
@import "flatpickr";
11
@import "flatpickr";
Lines 32-37 $selected: #B9DB88; Link Here
32
    &.repeatableyearly {
35
    &.repeatableyearly {
33
        background-color: $repeatableyearly_bg;
36
        background-color: $repeatableyearly_bg;
34
    }
37
    }
38
39
    &.float {
40
        background-color: $float_bg;
41
    }
35
}
42
}
36
43
37
.flatpickr-day {
44
.flatpickr-day {
Lines 40-45 $selected: #B9DB88; Link Here
40
        border: 0;
47
        border: 0;
41
    }
48
    }
42
49
50
    &.past-date {
51
        background-color: $pastdate_bg;
52
53
        &.selected {
54
            border:3px solid $selected;
55
        }
56
57
        &:hover {
58
            background-color: #CCC;
59
        }
60
    }
61
43
    &.exception {
62
    &.exception {
44
        background-color: $exception_bg;
63
        background-color: $exception_bg;
45
64
Lines 71-76 $selected: #B9DB88; Link Here
71
            border: 3px solid $selected;
90
            border: 3px solid $selected;
72
        }
91
        }
73
    }
92
    }
93
94
    &.float {
95
        background-color: $float_bg;
96
97
        .selected {
98
            border: 3px solid $selected;
99
        }
100
    }
74
}
101
}
75
102
76
#holidayexceptions th.exception {
103
#holidayexceptions th.exception {
Lines 93-98 $selected: #B9DB88; Link Here
93
    background-color: $repeatableyearly_bg;
120
    background-color: $repeatableyearly_bg;
94
}
121
}
95
122
123
#holidaysfloat th.float {
124
    background-color: $float_bg;
125
}
126
96
.panel {
127
.panel {
97
    border: 1px solid #8AC363;
128
    border: 1px solid #8AC363;
98
    box-shadow: none;
129
    box-shadow: none;
Lines 146-148 fieldset.brief li.radio { Link Here
146
.dayContainer {
177
.dayContainer {
147
    gap: 3px;
178
    gap: 3px;
148
}
179
}
180
181
.calendar {
182
    display: flex;
183
    align-items: start;
184
    gap: 10px;
185
    flex-wrap: wrap;
186
}
187
188
#newHoliday {
189
    flex-grow: 1;
190
    margin-top: 2px;
191
    border-radius: 0;
192
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc (-1 / +1 lines)
Lines 58-64 Link Here
58
        <h5>Additional tools</h5>
58
        <h5>Additional tools</h5>
59
        <ul>
59
        <ul>
60
            [% IF ( CAN_user_tools_edit_calendar ) %]
60
            [% IF ( CAN_user_tools_edit_calendar ) %]
61
                <li><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></li>
61
                <li><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></li>
62
            [% END %]
62
            [% END %]
63
            [% IF ( CAN_user_tools_manage_csv_profiles ) %]
63
            [% IF ( CAN_user_tools_manage_csv_profiles ) %]
64
                <li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
64
                <li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt (+809 lines)
Line 0 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Branches %]
4
[% SET footerjs = 1 %]
5
[% INCLUDE 'doc-head-open.inc' %]
6
<title>[% Branches.GetName( branch ) | html %] calendar &rsaquo; Tools &rsaquo; Koha</title>
7
[% INCLUDE 'doc-head-close.inc' %]
8
[% Asset.css("css/calendar.css") | $raw %]
9
</head>
10
11
<body id="tools_holidays" class="tools">
12
[% WRAPPER 'header.inc' %]
13
    [% INCLUDE 'cat-search.inc' %]
14
[% END %]
15
16
[% WRAPPER 'sub-header.inc' %]
17
    [% WRAPPER breadcrumbs %]
18
        [% WRAPPER breadcrumb_item %]
19
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
20
        [% END %]
21
        [% WRAPPER breadcrumb_item bc_active= 1 %]
22
            <span>[% Branches.GetName( branch ) | html %] calendar</span>
23
        [% END %]
24
    [% END #/ WRAPPER breadcrumbs %]
25
[% END #/ WRAPPER sub-header.inc %]
26
27
<div id="main" class="main container-fluid">
28
    <div class="row">
29
        <div class="col-sm-10 col-sm-push-2">
30
            <main>
31
                [% IF no_branch_selected %]
32
                <div class="dialog alert">
33
                    <strong>No library set!</strong>
34
                </div>
35
                [% END %]
36
37
                [% UNLESS datesInfos %]
38
                <div class="dialog alert">
39
                    <strong>Error!</strong> You have to run add_days_discrete_calendar.pl in order to use Discrete Calendar.
40
                </div>
41
                [% END %]
42
43
                [% IF date_format_error %]
44
                <div class="dialog alert">
45
                    <strong>Error!</strong> Date format error. Please try again.
46
                </div>
47
                [% END %]
48
49
                [% IF cannot_edit_past_dates %]
50
                <div class="alert alert-danger">
51
                    <strong>Error!</strong> You cannot edit the past. The date '[% error_date | html %]' was out of range for the requested action.
52
                </div>
53
                [% END %]
54
55
                <h1>[% Branches.GetName( branch ) | html %] calendar</h1>
56
57
                <div class="row">
58
                    <div class="col-sm-6">
59
                        <div class="page-section">
60
                            <label for="branch">Define the holidays for:</label>
61
                            <form id="copyCalendar-form" method="post">
62
                                <select id="branch" name="branch">
63
                                    [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
64
                                </select>
65
                                Copy calendar to
66
                                <select id='newBranch' name ='newBranch'>
67
                                    <option value=""></option>
68
                                    [% FOREACH l IN Branches.all() %]
69
                                        [% UNLESS branch == l.branchcode %]
70
                                        <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
71
                                        [% END %]
72
                                    [% END %]
73
                                </select>
74
                                <input type="hidden" name="action" value="copyBranch" />
75
                                <input type="submit" value="Clone">
76
                            </form>
77
78
                            <h3>Calendar information</h3>
79
80
                            <div class="calendar">
81
                                <span id="calendar-anchor"></span>
82
83
                                <!-- ***************************** Panel to deal with new holidays ********************** -->
84
                                <div class="panel newHoliday" id="newHoliday">
85
                                    <form id="newHoliday-form" method="post">
86
                                        <fieldset class="brief">
87
                                            <h3>Edit date details</h3>
88
                                            <span id="holtype"></span>
89
                                            <ol>
90
                                                <li>
91
                                                    <strong>Library:</strong>
92
                                                    <span id="newBranchNameOutput"></span>
93
                                                    <input type="hidden" id="branch" name="branch" value="[% branch | html %]" />
94
                                                </li>
95
                                                <li>
96
                                                    <strong>From date:</strong>
97
                                                    <span id="newDaynameOutput"></span>,
98
99
                                                    [% IF ( dateformat == "us" ) %]
100
                                                        <span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>
101
                                                    [% ELSIF ( dateformat == "metric" ) %]
102
                                                        <span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>
103
                                                    [% ELSIF ( dateformat == "dmydot" ) %]
104
                                                        <span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>
105
                                                    [% ELSE %]
106
                                                        <span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>
107
                                                    [% END %]
108
109
                                                    <input type="hidden" id="newDayname" name="showDayname" />
110
                                                    <input type="hidden" id="Day" name="Day" />
111
                                                    <input type="hidden" id="Month" name="Month" />
112
                                                    <input type="hidden" id="Year" name="Year" />
113
                                                </li>
114
                                                <li class="dateinsert">
115
                                                    <strong>To date:</strong>
116
                                                    <input type="text" id="to_date_flatpickr" size="20" />
117
                                                </li>
118
                                                <li>
119
                                                    <label for="title">Title: </label>
120
                                                    <input type="text" name="Title" id="title" size="35" />
121
                                                </li>
122
                                                <li>
123
                                                    <label for="description">Description: </label>
124
                                                    <textarea id="description" name="description" rows="2" cols="40"></textarea>
125
                                                </li>
126
                                                <li id="holidayType">
127
                                                    <label for="holidayType">Date type</label>
128
                                                    <select name ='holidayType'>
129
                                                        <option value="empty"></option>
130
                                                        <option value="none">Working day</option>
131
                                                        <option value="E">Unique holiday</option>
132
                                                        <option value="W">Weekly holiday</option>
133
                                                        <option value="R">Repeatable holiday</option>
134
                                                        <option value="F">Floating holiday</option>
135
                                                        <option value="N" disabled>Need validation</option>
136
                                                    </select>
137
                                                    <a href="#" class="helptext">[?]</a>
138
                                                    <div class="hint">
139
                                                        <ol>
140
                                                            <li><strong>Working day:</strong> the library is open on that day.</li>
141
                                                            <li><strong>Unique holiday:</strong> make a single holiday. For example, selecting August 1, 2012 will make it a holiday, but will not affect August 1 in other years.</li>
142
                                                            <li><strong>Weekly holiday:</strong> make this weekday a holiday, every week. For example, if your library is closed on Saturdays, use this option to make every Saturday a holiday.</li>
143
                                                            <li><strong>Repeatable holiday:</strong> this will take this day and month as a reference to make it a holiday. Through this option, you can repeat this rule for every year. For example, selecting August 1 will make August 1 a holiday every year.</li>
144
                                                            <li><strong>Floating holiday:</strong> this will take this day and month as a reference to make it a floating holiday. Through this option, you can add a holiday that repeats every year but not necessarily on the exact same day. On subsequent years the date will need validation.</li>
145
                                                            <li><strong>Need validation:</strong> this holiday has been added automatically, but needs to be validated.</li>
146
                                                        </ol>
147
                                                    </div>
148
                                                </li>
149
                                                <li id="days_of_week">
150
                                                    <label for="day_of_week">Week day</label>
151
                                                    <select name ='day_of_week'>
152
                                                        <option value="everyday">Everyday</option>
153
                                                        <option value="1">Sundays</option>
154
                                                        <option value="2">Mondays</option>
155
                                                        <option value="3">Tuesdays</option>
156
                                                        <option value="4">Wednesdays</option>
157
                                                        <option value="5">Thursdays</option>
158
                                                        <option value="6">Fridays</option>
159
                                                        <option value="7">Saturdays</option>
160
                                                    </select>
161
                                                </li>
162
                                                <li class="radio" id="deleteType">
163
                                                    <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label>
164
                                                    <a href="#" class="helptext">[?]</a>
165
                                                    <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if working day is selected.</div>
166
                                                </li>
167
                                                <li>
168
                                                    <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' />
169
                                                </li>
170
                                                <li>
171
                                                    <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' />
172
                                                </li>
173
                                                <li class="radio">
174
                                                    <input type="radio" name="action" id="EditRadioButton" value="edit" checked/>
175
                                                    <label for="EditRadioButton">Edit selected dates</label>
176
                                                </li>
177
                                                <li class="radio">
178
                                                    <input type="radio" name="action" id="CopyRadioButton" value="copyDates" />
179
                                                    <label for="CopyRadioButton">Copy to different dates</label>
180
                                                </li>
181
                                                <li class="CopyDatePanel">
182
                                                    <label>From:</label>
183
                                                    <input type="text" id="copyto_from_flatpickr" size="20"/>
184
                                                    <label>To:</label>
185
                                                    <input type="text" id="copyto_to_flatpickr" size="20"/>
186
                                                </li>
187
                                                <li class="checkbox">
188
                                                    <input type="checkbox" name="all_branches" id="all_branches" />
189
                                                    <label for="all_branches">Copy to all libraries</label>.
190
                                                    <a href="#" class="helptext">[?]</a>
191
                                                    <div class="hint">If checked, this holiday will be copied to all libraries.</div>
192
                                                </li>
193
                                            </ol>
194
195
                                            <!-- These yyyy-mm-dd -->
196
                                            <input type="hidden" name="from_date" id='from_date'>
197
                                            <input type="hidden" name="to_date" id='to_date'>
198
                                            <input type="hidden" name="copyto_from" id='copyto_from'>
199
                                            <input type="hidden" name="copyto_to" id='copyto_to'>
200
                                            <input type="hidden" name="daysnumber" id='daysnumber'>
201
                                            <input type="hidden" name="local_today" id='local_today'>
202
203
                                            <fieldset class="action">
204
                                                <input type="submit" name="submit" value="Save" />
205
                                                <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
206
                                            </fieldset>
207
                                        </fieldset>
208
                                    </form>
209
                                </div>
210
                            </div>
211
                        </div> <!-- /.page-section -->
212
                    </div> <!-- /.col-sm-6 -->
213
214
                    <div class="col-sm-6">
215
                        <div class="page-section">
216
                            <div class="help">
217
                                <h4>Hints</h4>
218
                                <ul>
219
                                    <li>Search in the calendar the day you want to set as holiday.</li>
220
                                    <li>Click the date to add or edit a holiday.</li>
221
                                    <li>Enter a title and description for the holiday.</li>
222
                                    <li>Specify how the holiday should repeat.</li>
223
                                    <li>Click Save to finish.</li>
224
                                    <li>PS:
225
                                        <ul>
226
                                            <li>Past dates cannot be changed</li>
227
                                            <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li>
228
                                        </ul>
229
                                    </li>
230
                                </ul>
231
                                <h4>Key</h4>
232
                                <p>
233
                                    <span class="key normalday">Working day</span>
234
                                    <span class="key holiday">Unique holiday</span>
235
                                    <span class="key repeatableweekly">Holiday repeating weekly</span>
236
                                    <span class="key repeatableyearly">Holiday repeating yearly</span>
237
                                    <span class="key float">Floating holiday</span>
238
                                    <span class="key exception">Need validation</span>
239
                                </p>
240
                            </div> <!-- /#help -->
241
242
                            <div id="holiday-list">
243
                                [% IF ( NEED_VALIDATION_HOLIDAYS ) %]
244
                                <h3>Need validation holidays</h3>
245
                                <table id="holidayexceptions" class="dataTable no-footer">
246
                                    <thead>
247
                                        <tr>
248
                                            <th class="exception">Date</th>
249
                                            <th class="exception">Title</th>
250
                                            <th class="exception">Description</th>
251
                                        </tr>
252
                                    </thead>
253
                                    <tbody>
254
                                        [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %]
255
                                        <tr>
256
                                            <td><a href="#main" onclick="go_to_date('[% need_validation_holiday.date | html %]')"><span title="[% need_validation_holiday.DATE_SORT | html %]">[% need_validation_holiday.outputdate | html %]</span></a></td>
257
                                            <td>[% need_validation_holiday.note | html %]</td>
258
                                            <td>[% need_validation_holiday.description.replace('\\\r\\\n', '<br />') | html %]</td>
259
                                        </tr>
260
                                        [% END %]
261
                                    </tbody>
262
                                </table> <!-- /#holidayexceptions -->
263
                                [% END # /IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
264
265
                                [% IF ( WEEKLY_HOLIDAYS ) %]
266
                                <h3>Weekly - Repeatable holidays</h3>
267
                                <table id="holidayweeklyrepeatable" class="dataTable no-footer">
268
                                    <thead>
269
                                        <tr>
270
                                            <th class="repeatableweekly">Day of week</th>
271
                                            <th class="repeatableweekly">Title</th>
272
                                            <th class="repeatableweekly">Description</th>
273
                                        </tr>
274
                                    </thead>
275
                                    <tbody>
276
                                        [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %]
277
                                        <tr>
278
                                            <td>[% WEEK_DAYS_LOO.weekday | html %]</td>
279
                                            <td>[% WEEK_DAYS_LOO.note | html %]</td>
280
                                            <td>[% WEEK_DAYS_LOO.description.replace('\\\r\\\n', '<br />') | html %]</td>
281
                                        </tr>
282
                                        [% END %]
283
                                    </tbody>
284
                                </table> <!-- /#holidayweeklyrepeatable -->
285
                                [% END # / IF ( WEEKLY_HOLIDAYS ) %]
286
287
                                [% IF ( REPEATABLE_HOLIDAYS ) %]
288
                                <h3>Yearly - Repeatable holidays</h3>
289
                                <table id="holidaysyearlyrepeatable" class="dataTable no-footer">
290
                                    <thead>
291
                                        <tr>
292
                                            [% IF ( dateformat == "metric" ) %]
293
                                            <th class="repeatableyearly">Day/month</th>
294
                                            [% ELSE %]
295
                                            <th class="repeatableyearly">Month/day</th>
296
                                            [% END %]
297
                                            <th class="repeatableyearly">Title</th>
298
                                            <th class="repeatableyearly">Description</th>
299
                                        </tr>
300
                                    </thead>
301
                                    <tbody>
302
                                        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %]
303
                                        <tr>
304
                                            [% IF ( dateformat == "metric" ) %]
305
                                            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">[% DAY_MONTH_HOLIDAYS_LOO.day | html %]/[% DAY_MONTH_HOLIDAYS_LOO.month | html %]</span></td>
306
                                            [% ELSE %]
307
                                            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">[% DAY_MONTH_HOLIDAYS_LOO.month | html %]/[% DAY_MONTH_HOLIDAYS_LOO.day | html %]</span></td>
308
                                            [% END %]
309
                                            <td>[% DAY_MONTH_HOLIDAYS_LOO.note | html %]</td>
310
                                            <td>[% DAY_MONTH_HOLIDAYS_LOO.description.replace('\\\r\\\n', '<br />') | html %]</td>
311
                                        </tr>
312
                                        [% END %]
313
                                    </tbody>
314
                                </table> <!-- /#holidaysyearlyrepeatable -->
315
                                [% END # /IF ( REPEATABLE_HOLIDAYS ) %]
316
317
                                [% IF ( UNIQUE_HOLIDAYS ) %]
318
                                <h3>Unique holidays</h3>
319
                                <label class="controls">
320
                                    <input type="checkbox" name="show_past" id="show_past_holidaysunique" class="show_past" />
321
                                    Show past entries
322
                                </label>
323
                                <table id="holidaysunique" class="dataTable no-footer">
324
                                    <thead>
325
                                        <tr>
326
                                            <th class="holiday">Date</th>
327
                                            <th class="holiday">Title</th>
328
                                            <th class="holiday">Description</th>
329
                                        </tr>
330
                                    </thead>
331
                                    <tbody>
332
                                        [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %]
333
                                        <tr data-date="[% HOLIDAYS_LOO.date | html %]">
334
                                            <td><a href="#main" onclick="go_to_date('[% HOLIDAYS_LOO.date | html %]')"><span title="[% HOLIDAYS_LOO.DATE_SORT | html %]">[% HOLIDAYS_LOO.outputdate | html %]</span></a></td>
335
                                            <td>[% HOLIDAYS_LOO.note | html %]</td>
336
                                            <td>[% HOLIDAYS_LOO.description.replace('\\\r\\\n', '<br />') | html %]</td>
337
                                        </tr>
338
                                        [% END %]
339
                                    </tbody>
340
                                </table> <!-- /#holidaysunique -->
341
                                [% END # /IF ( UNIQUE_HOLIDAYS ) %]
342
343
                                [% IF ( FLOAT_HOLIDAYS ) %]
344
                                <h3>Floating holidays</h3>
345
                                <label class="controls">
346
                                    <input type="checkbox" name="show_past" id="show_past_holidaysfloat" class="show_past" />
347
                                    Show past entries
348
                                </label>
349
                                <table id="holidaysfloat" class="dataTable no-footer">
350
                                    <thead>
351
                                        <tr>
352
                                            <th class="float">Date</th>
353
                                            <th class="float">Title</th>
354
                                            <th class="float">Description</th>
355
                                        </tr>
356
                                    </thead>
357
                                    <tbody>
358
                                        [% FOREACH float_holiday IN FLOAT_HOLIDAYS %]
359
                                        <tr data-date="[% float_holiday.date | html %]">
360
                                            <td><a href="#main" onclick="go_to_date('[% float_holiday.date | html %]')"><span title="[% float_holiday.DATE_SORT | html %]">[% float_holiday.outputdate | html %]</span></a></td>
361
                                            <td>[% float_holiday.note | html %]</td>
362
                                            <td>[% float_holiday.description.replace('\\\r\\\n', '<br />') | html %]</td>
363
                                        </tr>
364
                                        [% END %]
365
                                    </tbody>
366
                                </table> <!-- /#holidaysfloat -->
367
                                [% END # /IF ( FLOAT_HOLIDAYS ) %]
368
                            </div> <!-- /#holiday-list -->
369
                        </div> <!-- /.page-section -->
370
                    </div> <!-- /.col-sm-6 -->
371
                </div> <!-- /.row -->
372
            </main>
373
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
374
375
        <div class="col-sm-2 col-sm-pull-10">
376
            <aside>
377
                [% INCLUDE 'tools-menu.inc' %]
378
            </aside>
379
        </div> <!-- .col-sm-2.col-sm-pull-10 -->
380
    </div> <!-- /.row -->
381
382
[% MACRO jsinclude BLOCK %]
383
    [% INCLUDE 'calendar.inc' %]
384
    [% INCLUDE 'datatables.inc' %]
385
    [% Asset.js("js/tools-menu.js") | $raw %]
386
    <script>
387
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
388
389
        // Array containing all the information about each date in the calendar.
390
        var datesInfos = new Array();
391
        [% FOREACH date IN datesInfos %]
392
            datesInfos["[% date.date | html %]"] = {
393
                title : "[% date.note | replace('"','\"') | html %]",
394
                description : "[% date.description | replace('"','\"') | replace( '\n', '\\n' ) | replace( '\r', '\\r' ) | html %]",
395
                outputdate : "[% date.outputdate | html %]",
396
                holiday_type:"[% date.holiday_type | html %]",
397
                open_hour: "[% date.open_hour | html %]",
398
                close_hour: "[% date.close_hour | html %]"
399
            };
400
        [% END %]
401
402
        /*
403
         * Displays the details of the selected date on a side panel
404
         */
405
        function showHoliday (date_obj, dateString, dayName, day, month, year, weekDay, title, description, holidayType) {
406
            $("#newHoliday").slideDown("fast");
407
            $("#copyHoliday").slideUp("fast");
408
            $('#newDaynameOutput').html(dayName);
409
            $('#newDayname').val(dayName);
410
            $('#newBranchNameOutput').html($("#branch :selected").text());
411
            $(".newHoliday").val($('#branch').val());
412
            $('#newDayOutput').html(day);
413
            $(".newHoliday #Day").val(day);
414
            $(".newHoliday #Month").val(month);
415
            $(".newHoliday #Year").val(year);
416
            $("#newMonthOutput").html(month);
417
            $("#newYearOutput").html(year);
418
            $(".newHoliday, #Weekday").val(weekDay);
419
420
            $('.newHoliday #title').val(title);
421
            $('.newHoliday #description').val(description);
422
            $('#HolidayType').val(holidayType);
423
            $('#days_of_week option[value="'+ (weekDay + 1) +'"]').attr('selected', true);
424
            $('#openHour').val(datesInfos[dateString].open_hour);
425
            $('#closeHour').val(datesInfos[dateString].close_hour);
426
            $('#local_today').val(getSeparetedDate(new Date()).dateString);
427
428
            // This changes the label of the date type on the edit panel
429
            if (holidayType == 'W') {
430
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
431
            } else if (holidayType == 'R') {
432
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
433
            } else if (holidayType == 'F') {
434
                $("#holtype").attr("class","key float").html(_("Floating holiday"));
435
            } else if (holidayType == 'N') {
436
                $("#holtype").attr("class","key exception").html(_("Needs validation"));
437
            } else if (holidayType == 'E') {
438
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
439
            } else {
440
                $("#holtype").attr("class","key normalday").html(_("Working day "));
441
            }
442
443
            // Select the correct holiday type on the dropdown menu
444
            if (datesInfos[dateString].holiday_type !='') {
445
                var type = datesInfos[dateString].holiday_type;
446
                $('#holidayType option[value="'+ type +'"]').attr('selected', true)
447
            } else {
448
                $('#holidayType option[value="none"]').attr('selected', true)
449
            }
450
451
            // If it is a weekly or repeatable holiday show the option to delete the type
452
            if (datesInfos[dateString].holiday_type == 'W' || datesInfos[dateString].holiday_type == 'R') {
453
                $('#deleteType').show("fast");
454
            } else {
455
                $('#deleteType').hide("fast");
456
            }
457
458
            // This value is to disable and hide input when the date is in the past, because you can't edit it.
459
            var value = false;
460
            var today = new Date();
461
            today.setHours(0, 0, 0, 0);
462
            if (date_obj < today ) {
463
                $("#holtype").attr("class","key past-date").html(_("Past date"));
464
                $("#CopyRadioButton").attr("checked", "checked");
465
                value = true;
466
                $(".CopyDatePanel").toggle(value);
467
            }
468
            $("#title").prop('disabled', value);
469
            $("#description").prop('disabled', value);
470
            $("#holidayType select").prop('disabled', value);
471
            $("#openHour").prop('disabled', value);
472
            $("#closeHour").prop('disabled', value);
473
            $("#EditRadioButton").parent().toggle(!value);
474
475
            // clear some fields
476
            $("#to_date_flatpickr").val('');
477
            document.querySelector('#to_date_flatpickr')._flatpickr.set('minDate', date_obj);
478
            $("#copyto_from_flatpickr").val('');
479
            $("#copyto_to_flatpickr").val('');
480
            $("#all_branches").prop('checked', '');
481
        }
482
483
        // This function gives css classes to each kind of day
484
        function dateStatusHandler(dayElem) {
485
            date = getSeparetedDate(dayElem.dateObj);
486
            var dateString = date.dateString;
487
            var today = new Date();
488
            today.setHours(0, 0, 0, 0);
489
490
            if (date.date_obj < today) {
491
                formatDay( [ "past-date", _("Past day")], dayElem );
492
            } else {
493
                formatDay( [ "normalday", _("Normal day")], dayElem );
494
            }
495
496
            if (datesInfos[dateString] && datesInfos[dateString].holiday_type =='W') {
497
                formatDay( [ "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)], dayElem );
498
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'R') {
499
                formatDay( [ "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)], dayElem );
500
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'N') {
501
                formatDay( [ "exception", _("Need validation: %s").format(datesInfos[dateString].title)], dayElem );
502
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'F') {
503
                formatDay( [ "float", _("Floating holiday: %s").format(datesInfos[dateString].title)], dayElem );
504
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'E') {
505
                formatDay( [ "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)], dayElem );
506
            }
507
        }
508
509
        function formatDay( settings, dayElem ){
510
            $(dayElem).attr("title", settings[1]).addClass( settings[0]);
511
        }
512
513
        /*
514
         * This function separate a given date object and returns an array containing all needed information about the date.
515
         */
516
        function getSeparetedDate(date) {
517
            var mydate = new Array();
518
            var day = (date.getDate() < 10 ? '0' : '') + date.getDate();
519
            var month = ((date.getMonth()+1) < 10 ? '0' : '') + (date.getMonth() +1);
520
            var year = date.getFullYear();
521
            var weekDay = date.getDay();
522
            // iso date string
523
            var dateString = year + '-' + month + '-' + day;
524
            mydate = {
525
                date_obj : date,
526
                dateString : dateString,
527
                weekDay: weekDay,
528
                year: year,
529
                month: month,
530
                day: day
531
            };
532
533
            return mydate;
534
        }
535
536
        /*
537
         * Validate the forms before sending them to the backend
538
         */
539
        function validateForm(form) {
540
            if (form =='newHoliday-form' && $('#CopyRadioButton').is(':checked')) {
541
                if ($('#copyto_from_flatpickr').val() =='' || $('#copyto_to_flatpickr').val() =='') {
542
                    alert("You have to pick a FROM and TO in the Copy to different dates.");
543
                    return false;
544
                } else if ($('#to_date_flatpickr').val()) {
545
                    var from_DateFrom = new Date(document.querySelector("#calendar-anchor")._flatpickr.selectedDates[0]);
546
                    var from_DateTo = new Date(document.querySelector('#to_date_flatpickr')._flatpickr.selectedDates[0]);
547
                    var to_DateFrom = new Date(document.querySelector('#copyto_from_flatpickr')._flatpickr.selectedDates[0]);
548
                    var to_DateTo = new Date(document.querySelector('#copyto_to_flatpickr')._flatpickr.selectedDates[0]);
549
550
                    var from_start = Math.round( from_DateFrom.getTime() / (3600*24*1000)); // days as integer from..
551
                    var from_end   = Math.round( from_DateTo.getTime() / (3600*24*1000));
552
                    var to_start = Math.round( to_DateFrom.getTime() / (3600*24*1000));
553
                    var to_end   = Math.round( to_DateTo.getTime() / (3600*24*1000));
554
555
                    var from_daysDiff = from_end - from_start +1;
556
                    var to_daysDiff = to_end - to_start + 1;
557
                    if (from_daysDiff == to_daysDiff) {
558
                        $('#daysnumber').val(to_daysDiff);
559
                        return true;
560
                    } else {
561
                        alert("You have to pick the same number of days if you choose 2 ranges");
562
                        return false;
563
                    }
564
                }
565
            } else if (form == 'copyCalendar-form') {
566
                if ($('#newBranch').val() =='') {
567
                    alert("Please select a copy to calendar.");
568
                    return false;
569
                } else {
570
                    return true;
571
                }
572
            } else {
573
                return true;
574
            }
575
        }
576
577
        function go_to_date(isoDate) {
578
            // I added the time to get around the timezone
579
            var date = getSeparetedDate(new Date(isoDate + " 00:00:00"));
580
            var day = date.day;
581
            var month = date.month;
582
            var year = date.year;
583
            var weekDay = date.weekDay;
584
            var dayName = weekdays[weekDay];
585
            var dateString = date.dateString;
586
            var date_obj = date.date_obj;
587
588
            document.querySelector("#calendar-anchor")._flatpickr.setDate(date_obj);
589
            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].description, datesInfos[dateString].holiday_type);
590
        }
591
592
        /*
593
         * Check if date range have the same opening, closing hours and holiday type if there's one.
594
         */
595
        function checkRange(date) {
596
            date = new Date(date);
597
            $('#to_date').val(getSeparetedDate(date).dateString);
598
            var fromDate = new Date(document.querySelector("#calendar-anchor")._flatpickr.selectedDates[0]);
599
            var sameHoliday =true;
600
            var sameOpenHours =true;
601
            var sameCloseHours =true;
602
603
            $('#days_of_week option[value="everyday"]').attr('selected', true);
604
            for (var i = fromDate; i <= date; i.setDate(i.getDate() + 1)) {
605
                var myDate1 = getSeparetedDate(i);
606
                var date1 = myDate1.dateString;
607
                var holidayType1 = datesInfos[date1].holiday_type;
608
                var open_hours1 = datesInfos[date1].open_hour;
609
                var close_hours1 = datesInfos[date1].close_hour;
610
                for (var j = fromDate; j <= date; j.setDate(j.getDate() + 1)) {
611
                    var myDate2 = getSeparetedDate(j);
612
                    var date2 = myDate2.dateString;
613
                    var holidayType2 = datesInfos[date2].holiday_type;
614
                    var open_hours2 = datesInfos[date2].open_hour;
615
                    var close_hours2 = datesInfos[date2].close_hour;
616
617
                    if (sameHoliday && holidayType1 != holidayType2) {
618
                        $('#holidayType option[value="empty"]').attr('selected', true);
619
                        sameHoliday=false;
620
                    }
621
                    if (sameOpenHours && (open_hours1 != open_hours2)) {
622
                        $('#openHour').val('');
623
                        sameOpenHours=false;
624
                    }
625
                    if (sameCloseHours && (close_hours1 != close_hours2)) {
626
                        $('#closeHour').val('');
627
                        sameCloseHours=false;
628
                    }
629
                }
630
                if (!sameOpenHours && !sameCloseHours && !sameHoliday) {
631
                    return false;
632
                }
633
            }
634
            return true;
635
        }
636
637
        /* Custom table search configuration: If a table row
638
            has an "expired" class, hide it UNLESS the
639
            show_expired checkbox is checked */
640
        $.fn.dataTable.ext.search.push(
641
            function( settings, searchData, index, rowData, counter ) {
642
                var table = settings.nTable.id;
643
                var row = $(settings.aoData[index].nTr);
644
                if( row.hasClass("date_past") && !$("#show_past_" + table ).prop("checked") ){
645
                    return false;
646
                } else {
647
                    return true;
648
                }
649
            }
650
        );
651
652
        // Create current date variable
653
        var date = new Date();
654
        var datestring = date.toISOString().substring(0, 10);
655
656
        $(document).ready(function() {
657
            $(".hint").hide();
658
            $("#days_of_week").hide();
659
            $("#deleteType").hide();
660
            $(".CopyDatePanel").hide();
661
662
            $("#branch").change(function() {
663
                var branch = $(this).find("option:selected").val();
664
                location.href = '/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate | html %]";
665
            });
666
667
            $("#holidayweeklyrepeatable>tbody>tr").each(function() {
668
                var first_td = $(this).find('td').first();
669
                var date_index = parseInt(first_td.html()) - 1;
670
                first_td.html(weekdays[date_index]);
671
            });
672
            $("#holidayweeklyrepeatable").dataTable($.extend(true, {}, dataTablesDefaults, {
673
                "sDom": 't',
674
                "bPaginate": false
675
            }));
676
            var tables = $("#holidaysyearlyrepeatable, #holidaysunique, #holidaysfloat").DataTable($.extend(true, {}, dataTablesDefaults, {
677
                "sDom": 't',
678
                "bPaginate": false,
679
                "createdRow": function( row, data, dataIndex ) {
680
                    var holiday = $(row).data("date");
681
                    if( holiday < datestring ){
682
                        $(row).addClass("date_past");
683
                    }
684
                }
685
            }));
686
687
            $(".show_past").on("change", function(){
688
                tables.draw();
689
            });
690
691
            $("a.helptext").click(function () {
692
                $(this).parent().find(".hint")
693
                    .css("max-width", ($(this).closest("#newHoliday").width() - 20)+"px")
694
                    .toggle();
695
                return false;
696
            });
697
698
            $("form").on("submit", function() {
699
                return validateForm($(this).attr("id"));
700
            });
701
702
            flatpickr.setDefaults({
703
                onDayCreate: function( dObj, dStr, fp, dayElem ){
704
                    /* for each day on the calendar, get the
705
                      correct status information for the date */
706
                    dateStatusHandler( dayElem );
707
                },
708
                minDate: new Date("[% minDate | html %]"),
709
                maxDate: new Date("[% maxDate | html %]")
710
            });
711
712
            // Main flatpickr
713
            $("#calendar-anchor").flatpickr({
714
                inline: true,
715
                onReady: function( selectedDates, dateStr, instance ){
716
                    // We do not want to display the 'close' icon in this case
717
                    $(instance.input).siblings('.flatpickr-input').hide();
718
                },
719
                onChange: function(selectedDates, dateStr, instance) {
720
                    [% IF datesInfos %]
721
                        var date = getSeparetedDate(selectedDates[0]);
722
                        var weekDay = date.weekDay;
723
                        var dayName = weekdays[weekDay];
724
                        var dateString = date.dateString;
725
726
                        // set value of form hidden field
727
                        $('#from_date').val(dateStr);
728
                        showHoliday(date.date_obj, dateString, dayName, date.day, date.month, date.year, weekDay, datesInfos[dateString].title, datesInfos[dateString].description, datesInfos[dateString].holiday_type);
729
                    [% END %]
730
                }
731
            });
732
733
            $('#to_date_flatpickr').flatpickr();
734
            $("#to_date_flatpickr").change(function() {
735
                checkRange(document.querySelector("#to_date_flatpickr")._flatpickr.selectedDates[0]);
736
                $('#to_date').val(($("#to_date_flatpickr").val()));
737
                if ($('#to_date_flatpickr').val()) {
738
                    $('#days_of_week').show("fast");
739
                } else {
740
                    $('#days_of_week').hide("fast");
741
                }
742
            });
743
744
            // Flatpickrs for copy dates feature
745
            $('#copyto_from_flatpickr').flatpickr({
746
                onChange: function(selectedDates, dateStr, instance) {
747
                    document.querySelector('#copyto_to_flatpickr')._flatpickr.set('minDate', dateStr);
748
                }
749
            });
750
            $("#copyto_from_flatpickr").change(function() {
751
                $('#copyto_from').val(($(this).val()));
752
            });
753
754
            $('#copyto_to_flatpickr').flatpickr();
755
            $("#copyto_to_flatpickr").change(function() {
756
                $('#copyto_to').val(($(this).val()));
757
            });
758
759
            // Flatpickrs for open and close hours
760
            timeoptions = {
761
                plugins: [],
762
                enableTime: true,
763
                noCalendar: true,
764
                dateFormat: "H:i:S",
765
                altInput: false,
766
                defaultHour: 12,
767
                defaultMinute: 0,
768
                onOpen: function( selectedDates, dateStr, instance ) {
769
                    instance.setDate(instance.input.value, false);
770
                }
771
            }
772
            $('#openHour').flatpickr(timeoptions);
773
            $('#closeHour').flatpickr(timeoptions);
774
775
            $('.newHoliday input[type="radio"]').click(function() {
776
                if ($(this).attr("id") == "CopyRadioButton") {
777
                    $(".CopyToBranchPanel").hide('fast');
778
                    $(".CopyDatePanel").show('fast');
779
                } else if ($(this).attr("id") == "CopyToBranchRadioButton") {
780
                    $(".CopyDatePanel").hide('fast');
781
                    $(".CopyToBranchPanel").show('fast');
782
                } else {
783
                    $(".CopyDatePanel").hide('fast');
784
                    $(".CopyToBranchPanel").hide('fast');
785
                }
786
            });
787
788
            $(".hidePanel").on("click", function() {
789
                $(this).closest(".panel").slideUp("fast");
790
            });
791
792
            $("#deleteType_checkbox").on("change", function() {
793
                if ($("#deleteType_checkbox").is(':checked')) {
794
                    $('#holidayType option[value="none"]').attr('selected', true);
795
                }
796
            });
797
798
            $("#holidayType select").on("change", function() {
799
                if ($("#holidayType select").val() == "R") {
800
                    $('#days_of_week').hide("fast");
801
                } else if ($('#to_date_flatpickr').val()) {
802
                    $('#days_of_week').show("fast");
803
                }
804
            });
805
        });
806
    </script>
807
[% END %]
808
809
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +1 lines)
Lines 122-128 Link Here
122
            [% END %]
122
            [% END %]
123
            <dl>
123
            <dl>
124
                [% IF ( CAN_user_tools_edit_calendar ) %]
124
                [% IF ( CAN_user_tools_edit_calendar ) %]
125
                    <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
125
                    <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
126
                    <dd>Define days when the library is closed</dd>
126
                    <dd>Define days when the library is closed</dd>
127
                [% END %]
127
                [% END %]
128
128
(-)a/misc/cronjobs/add_days_discrete_calendar.pl (+166 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
#
4
#   This script adds one day into discrete_calendar table based on the same day from the week before
5
#
6
use Modern::Perl;
7
use DateTime;
8
use DateTime::Format::Strptime;
9
use Data::Dumper;
10
use Getopt::Long;
11
use C4::Context;
12
use Koha::Database;
13
use Koha::DiscreteCalendar;
14
use Koha::DateUtils qw ( output_pref );
15
16
# Options
17
my $help = 0;
18
my $daysInFuture = 1;
19
my $branch = undef;
20
my $debug = 0;
21
GetOptions (
22
    'help|?|h'   => \$help,
23
    'n=i'        => \$daysInFuture,
24
    'b|branch=s' => \$branch,
25
    'd|debug'    => \$debug
26
);
27
28
my $usage = << 'ENDUSAGE';
29
30
This script adds days into discrete_calendar table based on the same day from the week before.
31
32
Examples :
33
    The latest date on discrete_calendar is : 28-07-2017
34
    The current date : 01-08-2016
35
    The dates that will be added are : 29-07-2017, 30-07-2017, 31-07-2017, 01-08-2017
36
Open close examples :
37
    Date added is : 29-07-2017
38
    Opening/closing hours will be base on : 22-07-2017 (- 7 days)
39
    Library open or closed will be based on : 29-07-2017 (- 1 year)
40
This script has the following parameters:
41
    -h --help: this message
42
    -n : number of days to add in the futre, default : 1
43
    -b --branch: branchcode of the library to which days will be added
44
    -d --debug: displays all added days and errors if there is any
45
46
ENDUSAGE
47
48
if ($help) {
49
    print $usage;
50
    exit;
51
}
52
53
my $schema = Koha::Database->new->schema;
54
$schema->storage->txn_begin;
55
my $dbh = C4::Context->dbh;
56
57
# Predeclaring variables that will be used several times in the code
58
my $query;
59
my $statement;
60
61
#getting the all the branches
62
my @branches = ();
63
if ( $branch ) {
64
    push @branches, $branch;
65
} else {
66
    $query = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode';
67
    $statement = $dbh->prepare($query);
68
    $statement->execute();
69
    for my $branchcode ( @{$statement->fetchall_arrayref} ) {
70
        push @branches, $branchcode->[0];
71
    }
72
}
73
74
foreach my $branchCode (@branches) {
75
    #get the latest date in the table
76
    $query = "SELECT MAX(date) FROM discrete_calendar WHERE branchcode = ?";
77
    $statement = $dbh->prepare($query);
78
    $statement->execute($branchCode);
79
    my $latestDate = $statement->fetchrow_array;
80
81
    if ( $latestDate ) {
82
        my $parser = DateTime::Format::Strptime->new(
83
            pattern => '%Y-%m-%d %H:%M:%S',
84
            on_error => 'croak',
85
        );
86
        $latestDate = $parser->parse_datetime($latestDate);
87
    } else {
88
        $latestDate = dt_from_string();
89
    }
90
91
    my $newDay = $latestDate->clone();
92
    $latestDate->add(days => $daysInFuture);
93
94
    for ($newDay->add(days => 1); $newDay <= $latestDate; $newDay->add(days => 1)) {
95
        my $lastWeekDay = $newDay->clone();
96
        $lastWeekDay->add(days=> -8);
97
        my $dayOfWeek = $lastWeekDay->day_of_week;
98
        # Representation fix
99
        # DateTime object dow (1-7) where Monday is 1
100
        # Arrays are 0-based where 0 = Sunday, not 7.
101
        $dayOfWeek -= 1 unless $dayOfWeek == 7;
102
        $dayOfWeek = 0 if $dayOfWeek == 7;
103
104
        #checking if it was open on the same day from last year
105
        my $yearAgo = $newDay->clone();
106
        $yearAgo = $yearAgo->add(years => -1);
107
        my $last_year = 'SELECT is_opened, holiday_type, note FROM discrete_calendar WHERE date=? AND branchcode=?';
108
        my $day_last_week = "SELECT open_hour, close_hour, holiday_type, note FROM discrete_calendar WHERE DAYOFWEEK(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1";
109
        my $add_Day = 'INSERT INTO discrete_calendar (date, branchcode, is_opened, open_hour, close_hour) VALUES (?, ?, ?, ?, ?)';
110
111
        #insert into discrete_calendar
112
        $statement = $dbh->prepare($last_year);
113
        $statement->execute($yearAgo, $branchCode);
114
        my ($is_opened, $holiday_type, $note) = $statement->fetchrow_array;
115
        #weekly and unique holidays are not replicated in the future
116
        if ( $holiday_type && $holiday_type ne "R" ) {
117
            $is_opened = 1;
118
            if ( $holiday_type eq "W" || $holiday_type eq "E" ) {
119
                $holiday_type='';
120
                $note='';
121
            } elsif ( $holiday_type eq "F" ) {
122
                $holiday_type = 'N';
123
                $is_opened = 0;
124
            }
125
        }
126
        $holiday_type = '' if $is_opened;
127
        $statement = $dbh->prepare($day_last_week);
128
        $statement->execute($newDay, $newDay);
129
        my ( $open_hour, $close_hour, $weekly_holiday_type, $weekly_note ) = $statement->fetchrow_array;
130
131
        # weekly repeatable holidays
132
        if ( $weekly_holiday_type && $weekly_holiday_type eq 'W' ) {
133
            $is_opened = 0;
134
            $holiday_type = $weekly_holiday_type unless $holiday_type;
135
            $note = $weekly_note unless $note;
136
        }
137
138
        my $data = {
139
            date       => output_pref( { dt => $newDay, dateformat => 'iso', timeformat => '24hr' }),
140
            branchcode => $branchCode,
141
        };
142
143
        $data->{is_opened}    = $is_opened    if ( defined $is_opened );
144
        $data->{holiday_type} = $holiday_type if ( defined $holiday_type );
145
        $data->{note}         = $note         if ( defined $note );
146
        $data->{open_hour}    = $open_hour    // "09:00:00";
147
        $data->{close_hour}   = $close_hour   // "17:00:00";
148
149
        my $calendar_date = $schema->resultset( "DiscreteCalendar" )->create( $data )->get_from_storage();
150
151
        if ( $debug && !$@ ) {
152
            warn "Added day " . $calendar_date->date
153
                . " to " . $calendar_date->branchcode
154
                . " is opened: " . $calendar_date->is_opened
155
                . ", holiday_type: " . $calendar_date->holiday_type
156
                . ", note: " . $calendar_date->note
157
                . ", open_hour: " . $calendar_date->open_hour
158
                . ", close_hour: " . $calendar_date->close_hour
159
                . " \n";
160
        } elsif ( $@ ) {
161
            warn "Failed to add day $newDay to $branchCode : $_\n";
162
        }
163
    }
164
}
165
# If everything went well we commit to the database
166
$schema->storage->txn_commit;
(-)a/misc/cronjobs/fines.pl (-2 / +2 lines)
Lines 38-45 use Carp qw( carp croak ); Link Here
38
use File::Spec;
38
use File::Spec;
39
use Try::Tiny qw( catch try );
39
use Try::Tiny qw( catch try );
40
40
41
use Koha::Calendar;
42
use Koha::DateUtils qw( dt_from_string output_pref );
41
use Koha::DateUtils qw( dt_from_string output_pref );
42
use Koha::DiscreteCalendar;
43
use Koha::Patrons;
43
use Koha::Patrons;
44
use C4::Log qw( cronlogaction );
44
use C4::Log qw( cronlogaction );
45
45
Lines 217-223 cronlogaction( { action => 'End', info => "COMPLETED" } ); Link Here
217
sub set_holiday {
217
sub set_holiday {
218
    my ( $branch, $dt ) = @_;
218
    my ( $branch, $dt ) = @_;
219
219
220
    my $calendar = Koha::Calendar->new( branchcode => $branch );
220
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
221
    return $calendar->is_holiday($dt);
221
    return $calendar->is_holiday($dt);
222
}
222
}
223
223
(-)a/misc/cronjobs/holds/cancel_unfilled_holds.pl (-1 / +2 lines)
Lines 25-31 use Koha::Script -cron; Link Here
25
use C4::Reserves;
25
use C4::Reserves;
26
use C4::Log qw( cronlogaction );
26
use C4::Log qw( cronlogaction );
27
use Koha::Holds;
27
use Koha::Holds;
28
use Koha::Calendar;
28
use Koha::DiscreteCalendar;
29
use Koha::DateUtils;
29
use Koha::Libraries;
30
use Koha::Libraries;
30
31
31
cronlogaction();
32
cronlogaction();
(-)a/misc/cronjobs/holds/holds_reminder.pl (-2 / +2 lines)
Lines 26-32 use C4::Context; Link Here
26
use C4::Letters;
26
use C4::Letters;
27
use C4::Log         qw( cronlogaction );
27
use C4::Log         qw( cronlogaction );
28
use Koha::DateUtils qw( dt_from_string );
28
use Koha::DateUtils qw( dt_from_string );
29
use Koha::Calendar;
29
use Koha::DiscreteCalendar;
30
use Koha::Libraries;
30
use Koha::Libraries;
31
use Koha::Notice::Templates;
31
use Koha::Notice::Templates;
32
use Koha::Patrons;
32
use Koha::Patrons;
Lines 242-248 foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP Link Here
242
    # If respecting calendar get the correct waiting since date
242
    # If respecting calendar get the correct waiting since date
243
    my $waiting_date;
243
    my $waiting_date;
244
    if ($use_calendar) {
244
    if ($use_calendar) {
245
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' );
245
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => 'Calendar' });
246
246
247
        #if today is a holiday skip sending the message
247
        #if today is a holiday skip sending the message
248
        next if $calendar->is_holiday($date_to_run);
248
        next if $calendar->is_holiday($date_to_run);
(-)a/misc/cronjobs/overdue_notices.pl (-2 / +2 lines)
Lines 33-39 use C4::Overdues qw( GetOverdueMessageTransportTypes parse_overdues_ Link Here
33
use C4::Log                  qw( cronlogaction );
33
use C4::Log                  qw( cronlogaction );
34
use Koha::Patron::Debarments qw( AddUniqueDebarment );
34
use Koha::Patron::Debarments qw( AddUniqueDebarment );
35
use Koha::DateUtils          qw( dt_from_string output_pref );
35
use Koha::DateUtils          qw( dt_from_string output_pref );
36
use Koha::Calendar;
36
use Koha::DiscreteCalendar;
37
use Koha::Libraries;
37
use Koha::Libraries;
38
use Koha::Acquisition::Currencies;
38
use Koha::Acquisition::Currencies;
39
use Koha::Patrons;
39
use Koha::Patrons;
Lines 486-492 my %seen = map { $_ => 1 } @branches; Link Here
486
foreach my $branchcode (@branches) {
486
foreach my $branchcode (@branches) {
487
    my $calendar;
487
    my $calendar;
488
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
488
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
489
        $calendar = Koha::Calendar->new( branchcode => $branchcode );
489
        $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
490
        if ( $calendar->is_holiday($date_to_run) ) {
490
        if ( $calendar->is_holiday($date_to_run) ) {
491
            next;
491
            next;
492
        }
492
        }
(-)a/misc/cronjobs/staticfines.pl (-2 / +2 lines)
Lines 32-38 use Date::Calc qw( Date_to_Days ); Link Here
32
use Koha::Script -cron;
32
use Koha::Script -cron;
33
use C4::Context;
33
use C4::Context;
34
use C4::Overdues    qw( CalcFine checkoverdues GetFine Getoverdues );
34
use C4::Overdues    qw( CalcFine checkoverdues GetFine Getoverdues );
35
use C4::Calendar    qw();                                               # don't need any exports from Calendar
35
use C4::DiscreteCalendar qw();                                          # don't need any exports from Calendar
36
use C4::Log         qw( cronlogaction );
36
use C4::Log         qw( cronlogaction );
37
use Getopt::Long    qw( GetOptions );
37
use Getopt::Long    qw( GetOptions );
38
use List::MoreUtils qw( none );
38
use List::MoreUtils qw( none );
Lines 178-184 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) { Link Here
178
178
179
    my $calendar;
179
    my $calendar;
180
    unless ( defined( $calendars{$branchcode} ) ) {
180
    unless ( defined( $calendars{$branchcode} ) ) {
181
        $calendars{$branchcode} = C4::Calendar->new( branchcode => $branchcode );
181
        $calendars{$branchcode} = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
182
    }
182
    }
183
    $calendar = $calendars{$branchcode};
183
    $calendar = $calendars{$branchcode};
184
    my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
184
    my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
(-)a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl (-2 / +2 lines)
Lines 27-34 use Koha::Script -cron; Link Here
27
use C4::Context;
27
use C4::Context;
28
use C4::Letters;
28
use C4::Letters;
29
use C4::Overdues;
29
use C4::Overdues;
30
use Koha::Calendar;
31
use Koha::DateUtils qw( dt_from_string output_pref );
30
use Koha::DateUtils qw( dt_from_string output_pref );
31
use Koha::DiscreteCalendar;
32
use Koha::Patrons;
32
use Koha::Patrons;
33
use Koha::Libraries;
33
use Koha::Libraries;
34
34
Lines 364-370 sub GetWaitingHolds { Link Here
364
            }
364
            }
365
        );
365
        );
366
366
367
        my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
367
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $issue->{'site'}, days_mode => $daysmode });
368
368
369
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
369
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
370
370
(-)a/tools/discrete_calendar.pl (+171 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
#####Sets holiday periods for each branch. Datedues will be extended if branch is closed -TG
19
use Modern::Perl;
20
21
use CGI qw ( -utf8 );
22
23
use C4::Auth qw( get_template_and_user );
24
use C4::Output qw( output_html_with_http_headers );
25
26
use Koha::DateUtils qw ( dt_from_string output_pref );
27
use Koha::DiscreteCalendar;
28
29
my $input = CGI->new;
30
31
# Get the template to use
32
my ($template, $loggedinuser, $cookie) = get_template_and_user(
33
    {
34
        template_name => "tools/discrete_calendar.tt",
35
        type => "intranet",
36
        query => $input,
37
        flagsrequired => {tools => 'edit_calendar'},
38
    }
39
);
40
41
my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
42
my $calendar = Koha::DiscreteCalendar->new({branchcode => $branch});
43
#alert the user that they are using the default calendar because they do not have a library set
44
my $no_branch_selected = $calendar->{no_branch_selected};
45
46
my $weekday = $input->param('day_of_week');
47
48
my $holiday_type = $input->param('holidayType');
49
my $allbranches = $input->param('allBranches');
50
51
my $title = $input->param('Title');
52
my $description = $input->param('description');
53
54
my $action = $input->param('action') || '';
55
56
# calendardate - date passed in url for human readability (syspref)
57
# if the url has an invalid date default to 'now.'
58
# FIXME There is something to improve in the date handling here
59
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate') ); } || dt_from_string;
60
my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
61
62
if ($action eq 'copyBranch') {
63
    my $new_branch = scalar $input->param('newBranch');
64
    $calendar->copy_to_branch($new_branch) if $new_branch;
65
} elsif($action eq 'copyDates') {
66
    my $from_startDate = $input->param('from_date') ||'';
67
    my $from_endDate = $input->param('to_date') || '';
68
    my $to_startDate = $input->param('copyto_from') || '';
69
    my $to_endDate = $input->param('copyto_to') || '';
70
    my $local_today = $input->param('local_today');
71
    my $daysnumber = $input->param('daysnumber');
72
73
    $from_startDate = eval { dt_from_string(scalar $from_startDate) } if $from_startDate ne '';
74
    $from_endDate = eval { dt_from_string(scalar $from_endDate) } if $from_endDate ne '';
75
    $to_startDate = eval { dt_from_string(scalar $to_startDate) } if $to_startDate ne '';
76
    $to_endDate = eval { dt_from_string(scalar $to_endDate) } if $to_endDate ne '';
77
    $local_today = eval { dt_from_string( $local_today, 'iso') };
78
79
    unless ($@) {
80
        my $diff_from_today = $local_today - $to_startDate;
81
        if ( $diff_from_today->is_positive ) {
82
            $template->param(
83
                cannot_edit_past_dates => 1,
84
                error_date => $to_startDate
85
            );
86
        }
87
        else {
88
            $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
89
        }
90
    } else {
91
        $template->param( date_format_error => 1 );
92
    }
93
} elsif ($action eq 'edit') {
94
    my $openHour = $input->param('openHour');
95
    my $closeHour = $input->param('closeHour');
96
    my $startDate = $input->param('from_date');
97
    my $endDate = $input->param('to_date');
98
    my $deleteType = $input->param('deleteType') || 0;
99
    my $all_branches = $input->param('all_branches') || 0;
100
    #Get today from javascript for a precise local time
101
    my $local_today = $input->param('local_today');
102
    $local_today = eval { dt_from_string( $local_today, 'iso') };
103
104
    $startDate = eval { dt_from_string(scalar $startDate) };
105
106
    unless ($@) {
107
        if($endDate ne '' ) {
108
            $endDate = eval { dt_from_string(scalar $endDate) };
109
        } else {
110
            $endDate = $startDate->clone();
111
        }
112
113
        $calendar->edit_holiday({
114
            title        => $title,
115
            description  => $description,
116
            weekday      => $weekday,
117
            holiday_type => $holiday_type,
118
            open_hour    => $openHour,
119
            close_hour   => $closeHour,
120
            start_date   => $startDate,
121
            end_date     => $endDate,
122
            delete_type  => $deleteType,
123
            all_branches => $all_branches,
124
            today        => $local_today
125
        });
126
    } else {
127
        $template->param( date_format_error => 1 );
128
    }
129
}
130
131
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
132
133
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
134
$keydate =~ s/-/\//g;
135
136
# Set all the branches.
137
if ( C4::Context->only_my_library ) {
138
    $branch = C4::Context->userenv->{'branch'};
139
}
140
141
# Get all the holidays
142
143
my @week_days = $calendar->get_week_days_holidays();
144
my @repeatable_holidays = $calendar->get_repeatable_holidays();
145
my @unique_holidays = $calendar->get_unique_holidays(0);
146
my @float_holidays = $calendar->get_float_holidays(0);
147
my @need_validation_holidays = $calendar->get_need_validation_holidays();
148
149
#Calendar minimum & maximum dates
150
my $minDate = $calendar->get_min_date();
151
my $maxDate = $calendar->get_max_date();
152
153
my @datesInfos = $calendar->get_dates_info();
154
155
$template->param(
156
    WEEKLY_HOLIDAYS          => \@week_days,
157
    REPEATABLE_HOLIDAYS      => \@repeatable_holidays,
158
    UNIQUE_HOLIDAYS          => \@unique_holidays,
159
    FLOAT_HOLIDAYS           => \@float_holidays,
160
    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
161
    calendardate             => $calendardate,
162
    keydate                  => $keydate,
163
    branch                   => $branch,
164
    minDate                  => $minDate,
165
    maxDate                  => $maxDate,
166
    datesInfos               => \@datesInfos,
167
    no_branch_selected       => $no_branch_selected,
168
);
169
170
# Shows the template with the real values replaced
171
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/exceptionHolidays.pl (-207 lines)
Lines 1-206 Link Here
1
#!/usr/bin/perl
2
3
use Modern::Perl;
4
5
use CGI qw ( -utf8 );
6
7
use C4::Auth qw( checkauth );
8
use C4::Output;
9
use DateTime;
10
11
use C4::Calendar;
12
use Koha::DateUtils qw( dt_from_string );
13
14
my $input = CGI->new;
15
my $op    = $input->param('op') // q{};
16
my $dbh   = C4::Context->dbh();
17
18
checkauth( $input, 0, { tools => 'edit_calendar' }, 'intranet' );
19
20
our $branchcode = $input->param('showBranchName');
21
my $originalbranchcode = $branchcode;
22
our $weekday     = $input->param('showWeekday');
23
our $day         = $input->param('showDay');
24
our $month       = $input->param('showMonth');
25
our $year        = $input->param('showYear');
26
our $title       = $input->param('showTitle');
27
our $description = $input->param('showDescription');
28
our $holidaytype = $input->param('showHolidayType');
29
my $datecancelrange_dt = eval { dt_from_string( scalar $input->param('datecancelrange') ) };
30
my $calendardate       = sprintf( "%04d-%02d-%02d", $year, $month, $day );
31
our $showoperation = $input->param('showOperation');
32
my $allbranches = $input->param('allBranches');
33
34
$title || ( $title = '' );
35
if ($description) {
36
    $description =~ s/\r/\\r/g;
37
    $description =~ s/\n/\\n/g;
38
} else {
39
    $description = '';
40
}
41
42
# We make an array with holiday's days
43
our @holiday_list;
44
if ($datecancelrange_dt) {
45
    my $first_dt = DateTime->new( year => $year, month => $month, day => $day );
46
47
    for ( my $dt = $first_dt->clone() ; $dt <= $datecancelrange_dt ; $dt->add( days => 1 ) ) {
48
        push @holiday_list, $dt->clone();
49
    }
50
}
51
52
if ( $op eq 'cud-edit' && $allbranches ) {
53
    my $libraries = Koha::Libraries->search;
54
    while ( my $library = $libraries->next ) {
55
        edit_holiday(
56
            $showoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description,
57
            $holidaytype,   @holiday_list
58
        );
59
    }
60
} elsif ( $op eq 'cud-edit' ) {
61
    edit_holiday(
62
        $showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype,
63
        @holiday_list
64
    );
65
}
66
67
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
68
69
sub edit_holiday {
70
    ( $showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list ) =
71
        @_;
72
    my $calendar = C4::Calendar->new( branchcode => $branchcode );
73
74
    if ( $showoperation eq 'exception' ) {
75
        $calendar->insert_exception_holiday(
76
            day         => $day,
77
            month       => $month,
78
            year        => $year,
79
            title       => $title,
80
            description => $description
81
        );
82
    } elsif ( $showoperation eq 'exceptionrange' ) {
83
        if (@holiday_list) {
84
            foreach my $date (@holiday_list) {
85
                $calendar->insert_exception_holiday(
86
                    day         => $date->{local_c}->{day},
87
                    month       => $date->{local_c}->{month},
88
                    year        => $date->{local_c}->{year},
89
                    title       => $title,
90
                    description => $description
91
                );
92
            }
93
        }
94
    } elsif ( $showoperation eq 'cud-edit' ) {
95
        if ( $holidaytype eq 'weekday' ) {
96
            my $isHoliday = $calendar->isHoliday( $day, $month, $year );
97
            if ($isHoliday) {
98
                $calendar->ModWeekdayholiday(
99
                    weekday     => $weekday,
100
                    title       => $title,
101
                    description => $description
102
                );
103
            } else {
104
                $calendar->insert_week_day_holiday(
105
                    weekday     => $weekday,
106
                    title       => $title,
107
                    description => $description
108
                );
109
            }
110
        } elsif ( $holidaytype eq 'daymonth' ) {
111
            my $isHoliday = $calendar->isHoliday( $day, $month, $year );
112
            if ($isHoliday) {
113
                $calendar->ModDaymonthholiday(
114
                    day         => $day,
115
                    month       => $month,
116
                    title       => $title,
117
                    description => $description
118
                );
119
            } else {
120
                $calendar->insert_day_month_holiday(
121
                    day         => $day,
122
                    month       => $month,
123
                    title       => $title,
124
                    description => $description
125
                );
126
            }
127
        } elsif ( $holidaytype eq 'ymd' ) {
128
            my $isHoliday = $calendar->isHoliday( $day, $month, $year );
129
            if ($isHoliday) {
130
                $calendar->ModSingleholiday(
131
                    day         => $day,
132
                    month       => $month,
133
                    year        => $year,
134
                    title       => $title,
135
                    description => $description
136
                );
137
            } else {
138
                $calendar->insert_single_holiday(
139
                    day         => $day,
140
                    month       => $month,
141
                    year        => $year,
142
                    title       => $title,
143
                    description => $description
144
                );
145
            }
146
        } elsif ( $holidaytype eq 'exception' ) {
147
            my $isHoliday = $calendar->isHoliday( $day, $month, $year );
148
            if ($isHoliday) {
149
                $calendar->ModExceptionholiday(
150
                    day         => $day,
151
                    month       => $month,
152
                    year        => $year,
153
                    title       => $title,
154
                    description => $description
155
                );
156
            } else {
157
                $calendar->insert_exception_holiday(
158
                    day         => $day,
159
                    month       => $month,
160
                    year        => $year,
161
                    title       => $title,
162
                    description => $description
163
                );
164
            }
165
        }
166
    } elsif ( $showoperation eq 'cud-delete' ) {
167
        $calendar->delete_holiday(
168
            weekday => $weekday,
169
            day     => $day,
170
            month   => $month,
171
            year    => $year
172
        );
173
    } elsif ( $showoperation eq 'deleterange' ) {
174
        if (@holiday_list) {
175
            foreach my $date (@holiday_list) {
176
                $calendar->delete_holiday_range(
177
                    weekday => $weekday,
178
                    day     => $date->{local_c}->{day},
179
                    month   => $date->{local_c}->{month},
180
                    year    => $date->{local_c}->{year}
181
                );
182
            }
183
        }
184
    } elsif ( $showoperation eq 'deleterangerepeat' ) {
185
        if (@holiday_list) {
186
            foreach my $date (@holiday_list) {
187
                $calendar->delete_holiday_range_repeatable(
188
                    weekday => $weekday,
189
                    day     => $date->{local_c}->{day},
190
                    month   => $date->{local_c}->{month}
191
                );
192
            }
193
        }
194
    } elsif ( $showoperation eq 'deleterangerepeatexcept' ) {
195
        if (@holiday_list) {
196
            foreach my $date (@holiday_list) {
197
                $calendar->delete_exception_holiday_range(
198
                    weekday => $weekday,
199
                    day     => $date->{local_c}->{day},
200
                    month   => $date->{local_c}->{month},
201
                    year    => $date->{local_c}->{year}
202
                );
203
            }
204
        }
205
    }
206
}
207
- 

Return to bug 17015