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

(-)a/C4/Calendar.pm (-789 lines)
Lines 1-789 Link Here
1
package C4::Calendar;
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
use strict;
19
use warnings;
20
use vars qw(@EXPORT);
21
22
use Carp       qw( croak );
23
use Date::Calc qw( Today );
24
25
use C4::Context;
26
use Koha::Caches;
27
28
use constant ISO_DATE_FORMAT => "%04d-%02d-%02d";
29
30
=head1 NAME
31
32
C4::Calendar::Calendar - Koha module dealing with holidays.
33
34
=head1 SYNOPSIS
35
36
    use C4::Calendar::Calendar;
37
38
=head1 DESCRIPTION
39
40
This package is used to deal with holidays. Through this package, you can set 
41
all kind of holidays for the library.
42
43
=head1 FUNCTIONS
44
45
=head2 new
46
47
  $calendar = C4::Calendar->new(branchcode => $branchcode);
48
49
Each library branch has its own Calendar.  
50
C<$branchcode> specifies which Calendar you want.
51
52
=cut
53
54
sub new {
55
    my $classname = shift @_;
56
    my %options   = @_;
57
    my $self      = bless( {}, $classname );
58
    foreach my $optionName ( keys %options ) {
59
        $self->{ lc($optionName) } = $options{$optionName};
60
    }
61
    defined( $self->{branchcode} )
62
        or croak "No branchcode argument to new.  Should be C4::Calendar->new(branchcode => \$branchcode)";
63
    $self->_init( $self->{branchcode} );
64
    return $self;
65
}
66
67
sub _init {
68
    my $self   = shift @_;
69
    my $branch = shift;
70
    defined($branch) or die "No branchcode sent to _init";    # must test for defined here and above to allow ""
71
    my $dbh        = C4::Context->dbh();
72
    my $repeatable = $dbh->prepare(
73
        'SELECT *
74
                                       FROM repeatable_holidays
75
                                      WHERE ( branchcode = ? )
76
                                        AND (ISNULL(weekday) = ?)'
77
    );
78
    $repeatable->execute( $branch, 0 );
79
    my %week_days_holidays;
80
81
    while ( my $row = $repeatable->fetchrow_hashref ) {
82
        my $key = $row->{weekday};
83
        $week_days_holidays{$key}{title}       = $row->{title};
84
        $week_days_holidays{$key}{description} = $row->{description};
85
    }
86
    $self->{'week_days_holidays'} = \%week_days_holidays;
87
88
    $repeatable->execute( $branch, 1 );
89
    my %day_month_holidays;
90
    while ( my $row = $repeatable->fetchrow_hashref ) {
91
        my $key = $row->{month} . "/" . $row->{day};
92
        $day_month_holidays{$key}{title}       = $row->{title};
93
        $day_month_holidays{$key}{description} = $row->{description};
94
        $day_month_holidays{$key}{day}         = sprintf( "%02d", $row->{day} );
95
        $day_month_holidays{$key}{month}       = sprintf( "%02d", $row->{month} );
96
    }
97
    $self->{'day_month_holidays'} = \%day_month_holidays;
98
99
    my $special = $dbh->prepare(
100
        'SELECT day, month, year, title, description
101
                                    FROM special_holidays
102
                                   WHERE ( branchcode = ? )
103
                                     AND (isexception = ?)'
104
    );
105
    $special->execute( $branch, 1 );
106
    my %exception_holidays;
107
    while ( my ( $day, $month, $year, $title, $description ) = $special->fetchrow ) {
108
        $exception_holidays{"$year/$month/$day"}{title}       = $title;
109
        $exception_holidays{"$year/$month/$day"}{description} = $description;
110
        $exception_holidays{"$year/$month/$day"}{date} =
111
            sprintf( ISO_DATE_FORMAT, $year, $month, $day );
112
    }
113
    $self->{'exception_holidays'} = \%exception_holidays;
114
115
    $special->execute( $branch, 0 );
116
    my %single_holidays;
117
    while ( my ( $day, $month, $year, $title, $description ) = $special->fetchrow ) {
118
        $single_holidays{"$year/$month/$day"}{title}       = $title;
119
        $single_holidays{"$year/$month/$day"}{description} = $description;
120
        $single_holidays{"$year/$month/$day"}{date} =
121
            sprintf( ISO_DATE_FORMAT, $year, $month, $day );
122
    }
123
    $self->{'single_holidays'} = \%single_holidays;
124
    return $self;
125
}
126
127
=head2 get_week_days_holidays
128
129
   $week_days_holidays = $calendar->get_week_days_holidays();
130
131
Returns a hash reference to week days holidays.
132
133
=cut
134
135
sub get_week_days_holidays {
136
    my $self               = shift @_;
137
    my $week_days_holidays = $self->{'week_days_holidays'};
138
    return $week_days_holidays;
139
}
140
141
=head2 get_day_month_holidays
142
143
   $day_month_holidays = $calendar->get_day_month_holidays();
144
145
Returns a hash reference to day month holidays.
146
147
=cut
148
149
sub get_day_month_holidays {
150
    my $self               = shift @_;
151
    my $day_month_holidays = $self->{'day_month_holidays'};
152
    return $day_month_holidays;
153
}
154
155
=head2 get_exception_holidays
156
157
    $exception_holidays = $calendar->exception_holidays();
158
159
Returns a hash reference to exception holidays. This kind of days are those
160
which stands for a holiday, but you wanted to make an exception for this particular
161
date.
162
163
=cut
164
165
sub get_exception_holidays {
166
    my $self               = shift @_;
167
    my $exception_holidays = $self->{'exception_holidays'};
168
    return $exception_holidays;
169
}
170
171
=head2 get_single_holidays
172
173
    $single_holidays = $calendar->get_single_holidays();
174
175
Returns a hash reference to single holidays. This kind of holidays are those which
176
happened just one time.
177
178
=cut
179
180
sub get_single_holidays {
181
    my $self            = shift @_;
182
    my $single_holidays = $self->{'single_holidays'};
183
    return $single_holidays;
184
}
185
186
=head2 insert_week_day_holiday
187
188
    insert_week_day_holiday(weekday => $weekday,
189
                            title => $title,
190
                            description => $description);
191
192
Inserts a new week day for $self->{branchcode}.
193
194
C<$day> Is the week day to make holiday.
195
196
C<$title> Is the title to store for the holiday formed by $year/$month/$day.
197
198
C<$description> Is the description to store for the holiday formed by $year/$month/$day.
199
200
=cut
201
202
sub insert_week_day_holiday {
203
    my $self    = shift @_;
204
    my %options = @_;
205
206
    my $weekday = $options{weekday};
207
    croak "Invalid weekday $weekday" unless $weekday =~ m/^[0-6]$/;
208
209
    my $dbh           = C4::Context->dbh();
210
    my $insertHoliday = $dbh->prepare(
211
        "insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values ( ?,?,NULL,NULL,?,? )"
212
    );
213
    $insertHoliday->execute( $self->{branchcode}, $weekday, $options{title}, $options{description} );
214
    $self->{'week_days_holidays'}->{$weekday}{title}       = $options{title};
215
    $self->{'week_days_holidays'}->{$weekday}{description} = $options{description};
216
    return $self;
217
}
218
219
=head2 insert_day_month_holiday
220
221
    insert_day_month_holiday(day => $day,
222
                             month => $month,
223
                             title => $title,
224
                             description => $description);
225
226
Inserts a new day month holiday for $self->{branchcode}.
227
228
C<$day> Is the day month to make the date to insert.
229
230
C<$month> Is month to make the date to insert.
231
232
C<$title> Is the title to store for the holiday formed by $year/$month/$day.
233
234
C<$description> Is the description to store for the holiday formed by $year/$month/$day.
235
236
=cut
237
238
sub insert_day_month_holiday {
239
    my $self    = shift @_;
240
    my %options = @_;
241
242
    my $dbh           = C4::Context->dbh();
243
    my $insertHoliday = $dbh->prepare(
244
        "insert into repeatable_holidays (branchcode,weekday,day,month,title,description) values (?, NULL, ?, ?, ?,? )"
245
    );
246
    $insertHoliday->execute(
247
        $self->{branchcode}, $options{day}, $options{month}, $options{title},
248
        $options{description}
249
    );
250
    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title}       = $options{title};
251
    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
252
    return $self;
253
}
254
255
=head2 insert_single_holiday
256
257
    insert_single_holiday(day => $day,
258
                          month => $month,
259
                          year => $year,
260
                          title => $title,
261
                          description => $description);
262
263
Inserts a new single holiday for $self->{branchcode}.
264
265
C<$day> Is the day month to make the date to insert.
266
267
C<$month> Is month to make the date to insert.
268
269
C<$year> Is year to make the date to insert.
270
271
C<$title> Is the title to store for the holiday formed by $year/$month/$day.
272
273
C<$description> Is the description to store for the holiday formed by $year/$month/$day.
274
275
=cut
276
277
sub insert_single_holiday {
278
    my $self    = shift @_;
279
    my %options = @_;
280
    @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
281
        if $options{date} && !$options{day};
282
283
    my $dbh           = C4::Context->dbh();
284
    my $isexception   = 0;
285
    my $insertHoliday = $dbh->prepare(
286
        "insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"
287
    );
288
    $insertHoliday->execute(
289
        $self->{branchcode}, $options{day}, $options{month}, $options{year}, $isexception,
290
        $options{title},     $options{description}
291
    );
292
    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title}       = $options{title};
293
    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
294
295
    # changed the 'single_holidays' table, lets force/reset its cache
296
    my $cache = Koha::Caches->get_instance();
297
    my $key   = $self->{branchcode} . "_holidays";
298
    $cache->clear_from_cache($key);
299
300
    return $self;
301
302
}
303
304
=head2 insert_exception_holiday
305
306
    insert_exception_holiday(day => $day,
307
                             month => $month,
308
                             year => $year,
309
                             title => $title,
310
                             description => $description);
311
312
Inserts a new exception holiday for $self->{branchcode}.
313
314
C<$day> Is the day month to make the date to insert.
315
316
C<$month> Is month to make the date to insert.
317
318
C<$year> Is year to make the date to insert.
319
320
C<$title> Is the title to store for the holiday formed by $year/$month/$day.
321
322
C<$description> Is the description to store for the holiday formed by $year/$month/$day.
323
324
=cut
325
326
sub insert_exception_holiday {
327
    my $self    = shift @_;
328
    my %options = @_;
329
330
    @options{qw(year month day)} = ( $options{date} =~ m/(\d+)-(\d+)-(\d+)/o )
331
        if $options{date} && !$options{day};
332
333
    my $dbh             = C4::Context->dbh();
334
    my $isexception     = 1;
335
    my $insertException = $dbh->prepare(
336
        "insert into special_holidays (branchcode,day,month,year,isexception,title,description) values (?,?,?,?,?,?,?)"
337
    );
338
    $insertException->execute(
339
        $self->{branchcode}, $options{day}, $options{month}, $options{year}, $isexception,
340
        $options{title},     $options{description}
341
    );
342
    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
343
    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} =
344
        $options{description};
345
346
    # changed the 'single_holidays' table, lets force/reset its cache
347
    my $cache = Koha::Caches->get_instance();
348
    my $key   = $self->{branchcode} . "_holidays";
349
    $cache->clear_from_cache($key);
350
351
    return $self;
352
}
353
354
=head2 ModWeekdayholiday
355
356
    ModWeekdayholiday(weekday =>$weekday,
357
                      title => $title,
358
                      description => $description)
359
360
Modifies the title and description of a weekday for $self->{branchcode}.
361
362
C<$weekday> Is the title to update for the holiday.
363
364
C<$description> Is the description to update for the holiday.
365
366
=cut
367
368
sub ModWeekdayholiday {
369
    my $self    = shift @_;
370
    my %options = @_;
371
372
    my $dbh = C4::Context->dbh();
373
    my $updateHoliday =
374
        $dbh->prepare("UPDATE repeatable_holidays SET title = ?, description = ? WHERE branchcode = ? AND weekday = ?");
375
    $updateHoliday->execute( $options{title}, $options{description}, $self->{branchcode}, $options{weekday} );
376
    $self->{'week_days_holidays'}->{ $options{weekday} }{title}       = $options{title};
377
    $self->{'week_days_holidays'}->{ $options{weekday} }{description} = $options{description};
378
    return $self;
379
}
380
381
=head2 ModDaymonthholiday
382
383
    ModDaymonthholiday(day => $day,
384
                       month => $month,
385
                       title => $title,
386
                       description => $description);
387
388
Modifies the title and description for a day/month holiday for $self->{branchcode}.
389
390
C<$day> The day of the month for the update.
391
392
C<$month> The month to be used for the update.
393
394
C<$title> The title to be updated for the holiday.
395
396
C<$description> The description to be update for the holiday.
397
398
=cut
399
400
sub ModDaymonthholiday {
401
    my $self    = shift @_;
402
    my %options = @_;
403
404
    my $dbh           = C4::Context->dbh();
405
    my $updateHoliday = $dbh->prepare(
406
        "UPDATE repeatable_holidays SET title = ?, description = ? WHERE month = ? AND day = ? AND branchcode = ?");
407
    $updateHoliday->execute(
408
        $options{title}, $options{description}, $options{month}, $options{day},
409
        $self->{branchcode}
410
    );
411
    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title}       = $options{title};
412
    $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
413
    return $self;
414
}
415
416
=head2 ModSingleholiday
417
418
    ModSingleholiday(day => $day,
419
                     month => $month,
420
                     year => $year,
421
                     title => $title,
422
                     description => $description);
423
424
Modifies the title and description for a single holiday for $self->{branchcode}.
425
426
C<$day> Is the day of the month to make the update.
427
428
C<$month> Is the month to make the update.
429
430
C<$year> Is the year to make the update.
431
432
C<$title> Is the title to update for the holiday formed by $year/$month/$day.
433
434
C<$description> Is the description to update for the holiday formed by $year/$month/$day.
435
436
=cut
437
438
sub ModSingleholiday {
439
    my $self    = shift @_;
440
    my %options = @_;
441
442
    my $dbh         = C4::Context->dbh();
443
    my $isexception = 0;
444
445
    my $updateHoliday = $dbh->prepare( "
446
UPDATE special_holidays SET title = ?, description = ?
447
    WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?" );
448
    $updateHoliday->execute(
449
        $options{title},     $options{description}, $options{day}, $options{month}, $options{year},
450
        $self->{branchcode}, $isexception
451
    );
452
    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title}       = $options{title};
453
    $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
454
455
    # changed the 'single_holidays' table, lets force/reset its cache
456
    my $cache = Koha::Caches->get_instance();
457
    my $key   = $self->{branchcode} . "_holidays";
458
    $cache->clear_from_cache($key);
459
460
    return $self;
461
}
462
463
=head2 ModExceptionholiday
464
465
    ModExceptionholiday(day => $day,
466
                        month => $month,
467
                        year => $year,
468
                        title => $title,
469
                        description => $description);
470
471
Modifies the title and description for an exception holiday for $self->{branchcode}.
472
473
C<$day> Is the day of the month for the holiday.
474
475
C<$month> Is the month for the holiday.
476
477
C<$year> Is the year for the holiday.
478
479
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
480
481
C<$description> Is the description to be modified for the holiday formed by $year/$month/$day.
482
483
=cut
484
485
sub ModExceptionholiday {
486
    my $self    = shift @_;
487
    my %options = @_;
488
489
    my $dbh           = C4::Context->dbh();
490
    my $isexception   = 1;
491
    my $updateHoliday = $dbh->prepare( "
492
UPDATE special_holidays SET title = ?, description = ?
493
    WHERE day = ? AND month = ? AND year = ? AND branchcode = ? AND isexception = ?" );
494
    $updateHoliday->execute(
495
        $options{title},     $options{description}, $options{day}, $options{month}, $options{year},
496
        $self->{branchcode}, $isexception
497
    );
498
    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
499
    $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} =
500
        $options{description};
501
502
    # changed the 'single_holidays' table, lets force/reset its cache
503
    my $cache = Koha::Caches->get_instance();
504
    my $key   = $self->{branchcode} . "_holidays";
505
    $cache->clear_from_cache($key);
506
507
    return $self;
508
}
509
510
=head2 delete_holiday
511
512
    delete_holiday(weekday => $weekday
513
                   day => $day,
514
                   month => $month,
515
                   year => $year);
516
517
Delete a holiday for $self->{branchcode}.
518
519
C<$weekday> Is the week day to delete.
520
521
C<$day> Is the day month to make the date to delete.
522
523
C<$month> Is month to make the date to delete.
524
525
C<$year> Is year to make the date to delete.
526
527
=cut
528
529
sub delete_holiday {
530
    my $self    = shift @_;
531
    my %options = @_;
532
533
    # Verify what kind of holiday that day is. For example, if it is
534
    # a repeatable holiday, this should check if there are some exception
535
    # for that holiday rule. Otherwise, if it is a regular holiday, it´s
536
    # ok just deleting it.
537
538
    my $dbh             = C4::Context->dbh();
539
    my $isSingleHoliday = $dbh->prepare(
540
        "SELECT id FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
541
    $isSingleHoliday->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
542
    if ( $isSingleHoliday->rows ) {
543
        my $id = $isSingleHoliday->fetchrow;
544
        $isSingleHoliday->finish;    # Close the last query
545
546
        my $deleteHoliday = $dbh->prepare("DELETE FROM special_holidays WHERE id = ?");
547
        $deleteHoliday->execute($id);
548
        delete( $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"} );
549
    } else {
550
        $isSingleHoliday->finish;    # Close the last query
551
552
        my $isWeekdayHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE branchcode = ? AND weekday = ?");
553
        $isWeekdayHoliday->execute( $self->{branchcode}, $options{weekday} );
554
        if ( $isWeekdayHoliday->rows ) {
555
            my $id = $isWeekdayHoliday->fetchrow;
556
            $isWeekdayHoliday->finish;    # Close the last query
557
558
            my $updateExceptions = $dbh->prepare(
559
                "UPDATE special_holidays SET isexception = 0 WHERE (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = ?) AND (branchcode = ?)"
560
            );
561
            $updateExceptions->execute( $options{weekday}, $self->{branchcode} );
562
            $updateExceptions->finish;    # Close the last query
563
564
            my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE id = ?");
565
            $deleteHoliday->execute($id);
566
            delete( $self->{'week_days_holidays'}->{ $options{weekday} } );
567
        } else {
568
            $isWeekdayHoliday->finish;    # Close the last query
569
570
            my $isDayMonthHoliday = $dbh->prepare(
571
                "SELECT id FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
572
            $isDayMonthHoliday->execute( $self->{branchcode}, $options{day}, $options{month} );
573
            if ( $isDayMonthHoliday->rows ) {
574
                my $id = $isDayMonthHoliday->fetchrow;
575
                $isDayMonthHoliday->finish;
576
                my $updateExceptions = $dbh->prepare(
577
                    "UPDATE special_holidays SET isexception = 0 WHERE (special_holidays.branchcode = ?) AND (special_holidays.day = ?) and (special_holidays.month = ?)"
578
                );
579
                $updateExceptions->execute( $self->{branchcode}, $options{day}, $options{month} );
580
                $updateExceptions->finish;    # Close the last query
581
582
                my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (id = ?)");
583
                $deleteHoliday->execute($id);
584
                delete( $self->{'day_month_holidays'}->{"$options{month}/$options{day}"} );
585
            }
586
        }
587
    }
588
589
    # changed the 'single_holidays' table, lets force/reset its cache
590
    my $cache = Koha::Caches->get_instance();
591
    my $key   = $self->{branchcode} . "_holidays";
592
    $cache->clear_from_cache($key);
593
594
    return $self;
595
}
596
597
=head2 delete_holiday_range
598
599
    delete_holiday_range(day => $day,
600
                   month => $month,
601
                   year => $year);
602
603
Delete a holiday range of dates for $self->{branchcode}.
604
605
C<$day> Is the day month to make the date to delete.
606
607
C<$month> Is month to make the date to delete.
608
609
C<$year> Is year to make the date to delete.
610
611
=cut
612
613
sub delete_holiday_range {
614
    my $self    = shift;
615
    my %options = @_;
616
617
    my $dbh = C4::Context->dbh();
618
    my $sth = $dbh->prepare(
619
        "DELETE FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
620
    $sth->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
621
622
    # changed the 'single_holidays' table, lets force/reset its cache
623
    my $cache = Koha::Caches->get_instance();
624
    my $key   = $self->{branchcode} . "_holidays";
625
    $cache->clear_from_cache($key);
626
627
}
628
629
=head2 delete_holiday_range_repeatable
630
631
    delete_holiday_range_repeatable(day => $day,
632
                   month => $month);
633
634
Delete a holiday for $self->{branchcode}.
635
636
C<$day> Is the day month to make the date to delete.
637
638
C<$month> Is month to make the date to delete.
639
640
=cut
641
642
sub delete_holiday_range_repeatable {
643
    my $self    = shift;
644
    my %options = @_;
645
646
    my $dbh = C4::Context->dbh();
647
    my $sth = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
648
    $sth->execute( $self->{branchcode}, $options{day}, $options{month} );
649
650
    # changed the 'single_holidays' table, lets force/reset its cache
651
    my $cache = Koha::Caches->get_instance();
652
    my $key   = $self->{branchcode} . "_holidays";
653
    $cache->clear_from_cache($key);
654
}
655
656
=head2 delete_exception_holiday_range
657
658
    delete_exception_holiday_range(weekday => $weekday
659
                   day => $day,
660
                   month => $month,
661
                   year => $year);
662
663
Delete a holiday for $self->{branchcode}.
664
665
C<$day> Is the day month to make the date to delete.
666
667
C<$month> Is month to make the date to delete.
668
669
C<$year> Is year to make the date to delete.
670
671
=cut
672
673
sub delete_exception_holiday_range {
674
    my $self    = shift;
675
    my %options = @_;
676
677
    my $dbh = C4::Context->dbh();
678
    my $sth = $dbh->prepare(
679
        "DELETE FROM special_holidays WHERE (branchcode = ?) AND (isexception = 1) AND (day = ?) AND (month = ?) AND (year = ?)"
680
    );
681
    $sth->execute( $self->{branchcode}, $options{day}, $options{month}, $options{year} );
682
683
    # changed the 'single_holidays' table, lets force/reset its cache
684
    my $cache = Koha::Caches->get_instance();
685
    my $key   = $self->{branchcode} . "_holidays";
686
    $cache->clear_from_cache($key);
687
}
688
689
=head2 isHoliday
690
691
    $isHoliday = isHoliday($day, $month $year);
692
693
C<$day> Is the day to check whether if is a holiday or not.
694
695
C<$month> Is the month to check whether if is a holiday or not.
696
697
C<$year> Is the year to check whether if is a holiday or not.
698
699
=cut
700
701
sub isHoliday {
702
    my ( $self, $day, $month, $year ) = @_;
703
704
    # FIXME - date strings are stored in non-padded metric format. should change to iso.
705
    $month = $month + 0;
706
    $year  = $year + 0;
707
    $day   = $day + 0;
708
    my $weekday    = &Date::Calc::Day_of_Week( $year, $month, $day ) % 7;
709
    my $weekDays   = $self->get_week_days_holidays();
710
    my $dayMonths  = $self->get_day_month_holidays();
711
    my $exceptions = $self->get_exception_holidays();
712
    my $singles    = $self->get_single_holidays();
713
714
    if ( defined( $exceptions->{"$year/$month/$day"} ) ) {
715
        return 0;
716
    } else {
717
        if (   ( exists( $weekDays->{$weekday} ) )
718
            || ( exists( $dayMonths->{"$month/$day"} ) )
719
            || ( exists( $singles->{"$year/$month/$day"} ) ) )
720
        {
721
            return 1;
722
        } else {
723
            return 0;
724
        }
725
    }
726
727
}
728
729
=head2 copy_to_branch
730
731
    $calendar->copy_to_branch($target_branch)
732
733
=cut
734
735
sub copy_to_branch {
736
    my ( $self, $target_branch ) = @_;
737
738
    croak "No target_branch" unless $target_branch;
739
740
    my $target_calendar = C4::Calendar->new( branchcode => $target_branch );
741
742
    my ( $y, $m, $d ) = Today();
743
    my $today = sprintf ISO_DATE_FORMAT, $y, $m, $d;
744
745
    my $wdh        = $self->get_week_days_holidays;
746
    my $target_wdh = $target_calendar->get_week_days_holidays;
747
    foreach my $key ( keys %$wdh ) {
748
        unless ( grep { $_ eq $key } keys %$target_wdh ) {
749
            $target_calendar->insert_week_day_holiday( weekday => $key, %{ $wdh->{$key} } );
750
        }
751
    }
752
753
    my $dmh        = $self->get_day_month_holidays;
754
    my $target_dmh = $target_calendar->get_day_month_holidays;
755
    foreach my $values ( values %$dmh ) {
756
        unless ( grep { $_->{day} eq $values->{day} && $_->{month} eq $values->{month} } values %$target_dmh ) {
757
            $target_calendar->insert_day_month_holiday( %{$values} );
758
        }
759
    }
760
761
    my $exception_holidays = $self->get_exception_holidays;
762
    my $target_exceptions  = $target_calendar->get_exception_holidays;
763
    foreach my $values ( grep { $_->{date} gt $today } values %{$exception_holidays} ) {
764
        unless ( grep { $_->{date} eq $values->{date} } values %$target_exceptions ) {
765
            $target_calendar->insert_exception_holiday( %{$values} );
766
        }
767
    }
768
769
    my $single_holidays = $self->get_single_holidays;
770
    my $target_singles  = $target_calendar->get_single_holidays;
771
    foreach my $values ( grep { $_->{date} gt $today } values %{$single_holidays} ) {
772
        unless ( grep { $_->{date} eq $values->{date} } values %$target_singles ) {
773
            $target_calendar->insert_single_holiday( %{$values} );
774
        }
775
    }
776
777
    return 1;
778
}
779
780
1;
781
782
__END__
783
784
=head1 AUTHOR
785
786
Koha Physics Library UNLP <matias_veleda@hotmail.com>
787
788
=cut
789
(-)a/C4/Circulation.pm (-8 / +7 lines)
Lines 43-49 use Koha::AuthorisedValues; Link Here
43
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
43
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
44
use Koha::Biblioitems;
44
use Koha::Biblioitems;
45
use Koha::DateUtils qw( dt_from_string );
45
use Koha::DateUtils qw( dt_from_string );
46
use Koha::Calendar;
46
use Koha::DiscreteCalendar;
47
use Koha::Checkouts;
47
use Koha::Checkouts;
48
use Koha::ILL::Requests;
48
use Koha::ILL::Requests;
49
use Koha::Items;
49
use Koha::Items;
Lines 1492-1498 sub checkHighHolds { Link Here
1492
                branchcode   => $branchcode,
1492
                branchcode   => $branchcode,
1493
            }
1493
            }
1494
        );
1494
        );
1495
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
1495
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
1496
1496
1497
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron );
1497
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron );
1498
1498
Lines 2855-2864 sub _calculate_new_debar_dt { Link Here
2855
2855
2856
        # Use the calendar or not to calculate the debarment date
2856
        # Use the calendar or not to calculate the debarment date
2857
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2857
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2858
            my $calendar = Koha::Calendar->new(
2858
            my $calendar = Koha::DiscreteCalendar->new({
2859
                branchcode => $branchcode,
2859
                branchcode => $branchcode,
2860
                days_mode  => 'Calendar'
2860
                days_mode  => 'Calendar'
2861
            );
2861
            });
2862
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2862
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2863
        } else {
2863
        } else {
2864
            $new_debar_dt = $return_date->clone()->add_duration($suspension_days);
2864
            $new_debar_dt = $return_date->clone()->add_duration($suspension_days);
Lines 4104-4110 sub CalcDateDue { Link Here
4104
        } else {    # days
4104
        } else {    # days
4105
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4105
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key} );
4106
        }
4106
        }
4107
        my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
4107
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
4108
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} ) if $dur;
4108
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} ) if $dur;
4109
        if ( $loanlength->{lengthunit} eq 'days' ) {
4109
        if ( $loanlength->{lengthunit} eq 'days' ) {
4110
            $datedue->set_hour(23);
4110
            $datedue->set_hour(23);
Lines 4142-4156 sub CalcDateDue { Link Here
4142
            }
4142
            }
4143
        }
4143
        }
4144
        if ( $daysmode ne 'Days' ) {
4144
        if ( $daysmode ne 'Days' ) {
4145
            my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
4145
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
4146
            if ( $calendar->is_holiday($datedue) ) {
4146
            if ( $calendar->is_holiday($datedue) ) {
4147
4147
4148
                # Don't return on a closed day
4148
                # Don't return on a closed day
4149
                $datedue = $calendar->prev_open_days( $datedue, 1 );
4149
                $datedue = $calendar->prev_open_day( $datedue )->set(hour => 23, minute => 59);
4150
            }
4150
            }
4151
        }
4151
        }
4152
    }
4152
    }
4153
4154
    return $datedue;
4153
    return $datedue;
4155
}
4154
}
4156
4155
(-)a/C4/HoldsQueue.pm (-1 / +8 lines)
Lines 79-84 sub TransportCostMatrix { Link Here
79
            cost             => $cost,
79
            cost             => $cost,
80
            disable_transfer => $disabled
80
            disable_transfer => $disabled
81
        };
81
        };
82
83
        if ( !my $ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
84
            my $calendars->{$from} ||= Koha::DiscreteCalendar->new({ branchcode => $from });
85
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
86
              $calendars->{$from}->is_holiday( $today );
87
        }
88
82
    }
89
    }
83
90
84
    return \%transport_cost_matrix;
91
    return \%transport_cost_matrix;
Lines 1092-1098 sub load_branches_to_pull_from { Link Here
1092
1099
1093
    my $today = dt_from_string();
1100
    my $today = dt_from_string();
1094
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
1101
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
1095
        @branches_to_use = grep { !Koha::Calendar->new( branchcode => $_ )->is_holiday($today) } @branches_to_use;
1102
        @branches_to_use = grep { !Koha::DiscreteCalendar->new({ branchcode => $_ })->is_holiday($today) } @branches_to_use;
1096
    }
1103
    }
1097
1104
1098
    return \@branches_to_use;
1105
    return \@branches_to_use;
(-)a/C4/Overdues.pm (-2 / +2 lines)
Lines 334-340 sub get_chargeable_units { Link Here
334
    my $charge_duration;
334
    my $charge_duration;
335
    if ( $unit eq 'hours' ) {
335
    if ( $unit eq 'hours' ) {
336
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
336
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
337
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
337
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
338
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
338
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
339
        } else {
339
        } else {
340
            $charge_duration = $date_returned->delta_ms($date_due);
340
            $charge_duration = $date_returned->delta_ms($date_due);
Lines 345-351 sub get_chargeable_units { Link Here
345
        return $charge_duration->in_units('hours');
345
        return $charge_duration->in_units('hours');
346
    } else {    # days
346
    } else {    # days
347
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
347
        if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
348
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
348
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
349
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
349
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
350
        } else {
350
        } else {
351
            $charge_duration = $date_returned->delta_days($date_due);
351
            $charge_duration = $date_returned->delta_days($date_due);
(-)a/C4/Reserves.pm (-2 / +2 lines)
Lines 34-44 use C4::Members; Link Here
34
use Koha::Account::Lines;
34
use Koha::Account::Lines;
35
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
35
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
36
use Koha::Biblios;
36
use Koha::Biblios;
37
use Koha::Calendar;
38
use Koha::Cache::Memory::Lite;
37
use Koha::Cache::Memory::Lite;
39
use Koha::CirculationRules;
38
use Koha::CirculationRules;
40
use Koha::Database;
39
use Koha::Database;
41
use Koha::DateUtils qw( dt_from_string output_pref );
40
use Koha::DateUtils qw( dt_from_string output_pref );
41
use Koha::DiscreteCalendar;
42
use Koha::Holds;
42
use Koha::Holds;
43
use Koha::ItemTypes;
43
use Koha::ItemTypes;
44
use Koha::Items;
44
use Koha::Items;
Lines 988-994 sub CancelExpiredReserves { Link Here
988
    my $holds = Koha::Holds->search($params);
988
    my $holds = Koha::Holds->search($params);
989
989
990
    while ( my $hold = $holds->next ) {
990
    while ( my $hold = $holds->next ) {
991
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
991
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
992
992
993
        next if !$cancel_on_holidays && $calendar->is_holiday($today);
993
        next if !$cancel_on_holidays && $calendar->is_holiday($today);
994
994
(-)a/Koha/Calendar.pm (-580 lines)
Lines 1-580 Link Here
1
package Koha::Calendar;
2
3
use Modern::Perl;
4
5
use Carp qw( croak );
6
use DateTime;
7
use DateTime::Duration;
8
use C4::Context;
9
use Koha::Caches;
10
use Koha::Exceptions;
11
use Koha::Exceptions::Calendar;
12
13
# This limit avoids an infinite loop when searching for an open day in an
14
# always closed library
15
# The value is arbitrary, but it should be large enough to be able to
16
# consider there is no open days if we haven't found any with that many
17
# iterations, and small enough to allow the loop to end quickly
18
# See next_open_days and prev_open_days
19
use constant OPEN_DAYS_SEARCH_MAX_ITERATIONS => 5000;
20
21
sub new {
22
    my ( $classname, %options ) = @_;
23
    my $self = {};
24
    bless $self, $classname;
25
    for my $o_name ( keys %options ) {
26
        my $o = lc $o_name;
27
        $self->{$o} = $options{$o_name};
28
    }
29
    if ( !defined $self->{branchcode} ) {
30
        croak 'No branchcode argument passed to Koha::Calendar->new';
31
    }
32
    $self->_init();
33
    return $self;
34
}
35
36
sub _init {
37
    my $self   = shift;
38
    my $branch = $self->{branchcode};
39
    my $dbh    = C4::Context->dbh();
40
    my $weekly_closed_days_sth =
41
        $dbh->prepare('SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL');
42
    $weekly_closed_days_sth->execute($branch);
43
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
44
    while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) {
45
        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
46
    }
47
    my $day_month_closed_days_sth =
48
        $dbh->prepare('SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL');
49
    $day_month_closed_days_sth->execute($branch);
50
    $self->{day_month_closed_days} = {};
51
    while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) {
52
        $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } =
53
            1;
54
    }
55
56
    $self->{test} = 0;
57
    return;
58
}
59
60
sub _holidays {
61
    my ($self) = @_;
62
63
    my $key      = $self->{branchcode} . "_holidays";
64
    my $cache    = Koha::Caches->get_instance();
65
    my $holidays = $cache->get_from_cache($key);
66
67
    # $holidays looks like:
68
    # {
69
    #    20131122 => 1, # single_holiday
70
    #    20131123 => 0, # exception_holiday
71
    #    ...
72
    # }
73
74
    # Populate the cache if necessary
75
    unless ($holidays) {
76
        my $dbh = C4::Context->dbh;
77
        $holidays = {};
78
79
        # Add holidays for each branch
80
        my $holidays_sth = $dbh->prepare(
81
            'SELECT day, month, year, MAX(isexception) FROM special_holidays WHERE branchcode = ? GROUP BY day, month, year'
82
        );
83
        $holidays_sth->execute( $self->{branchcode} );
84
85
        while ( my ( $day, $month, $year, $exception ) = $holidays_sth->fetchrow ) {
86
            my $datestring = sprintf( "%04d", $year ) . sprintf( "%02d", $month ) . sprintf( "%02d", $day );
87
88
            $holidays->{$datestring} = $exception ? 0 : 1;
89
        }
90
        $cache->set_in_cache( $key, $holidays, { expiry => 76800 } );
91
    }
92
    return $holidays // {};
93
}
94
95
sub addDuration {
96
    my ( $self, $startdate, $add_duration, $unit ) = @_;
97
98
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDuration: days_mode")
99
        unless exists $self->{days_mode};
100
101
    # Default to days duration (legacy support I guess)
102
    if ( ref $add_duration ne 'DateTime::Duration' ) {
103
        $add_duration = DateTime::Duration->new( days => $add_duration );
104
    }
105
106
    $unit ||= 'days';    # default days ?
107
    my $dt;
108
    if ( $unit eq 'hours' ) {
109
110
        # Fixed for legacy support. Should be set as a branch parameter
111
        my $return_by_hour = 10;
112
113
        $dt = $self->addHours( $startdate, $add_duration, $return_by_hour );
114
    } else {
115
116
        # days
117
        $dt = $self->addDays( $startdate, $add_duration );
118
    }
119
    return $dt;
120
}
121
122
sub addHours {
123
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
124
    my $base_date = $startdate->clone();
125
126
    $base_date->add_duration($hours_duration);
127
128
    # If we are using the calendar behave for now as if Datedue
129
    # was the chosen option (current intended behaviour)
130
131
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addHours: days_mode")
132
        unless exists $self->{days_mode};
133
134
    if (   $self->{days_mode} ne 'Days'
135
        && $self->is_holiday($base_date) )
136
    {
137
138
        if ( $hours_duration->is_negative() ) {
139
            $base_date = $self->prev_open_days( $base_date, 1 );
140
        } else {
141
            $base_date = $self->next_open_days( $base_date, 1 );
142
        }
143
144
        $base_date->set_hour($return_by_hour);
145
146
    }
147
148
    return $base_date;
149
}
150
151
sub addDays {
152
    my ( $self, $startdate, $days_duration ) = @_;
153
    my $base_date = $startdate->clone();
154
155
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDays: days_mode")
156
        unless exists $self->{days_mode};
157
158
    if ( $self->{days_mode} eq 'Calendar' ) {
159
160
        # use the calendar to skip all days the library is closed
161
        # when adding
162
        my $days = abs $days_duration->in_units('days');
163
164
        if ( $days_duration->is_negative() ) {
165
            while ($days) {
166
                $base_date = $self->prev_open_days( $base_date, 1 );
167
                --$days;
168
            }
169
        } else {
170
            while ($days) {
171
                $base_date = $self->next_open_days( $base_date, 1 );
172
                --$days;
173
            }
174
        }
175
176
    } else {    # Days, Datedue or Dayweek
177
                # use straight days, then use calendar to push
178
                # the date to the next open day as appropriate
179
                # if Datedue or Dayweek
180
        $base_date->add_duration($days_duration);
181
182
        if (   $self->{days_mode} eq 'Datedue'
183
            || $self->{days_mode} eq 'Dayweek' )
184
        {
185
            # Datedue or Dayweek, then use the calendar to push
186
            # the date to the next open day if holiday
187
            if ( $self->is_holiday($base_date) ) {
188
                my $dow  = $base_date->day_of_week;
189
                my $days = $days_duration->in_units('days');
190
191
                # Is it a period based on weeks
192
                my $push_amt = $days % 7 == 0 ? $self->get_push_amt($base_date) : 1;
193
                if ( $days_duration->is_negative() ) {
194
                    $base_date = $self->prev_open_days( $base_date, $push_amt );
195
                } else {
196
                    $base_date = $self->next_open_days( $base_date, $push_amt );
197
                }
198
            }
199
        }
200
    }
201
202
    return $base_date;
203
}
204
205
sub get_push_amt {
206
    my ( $self, $base_date ) = @_;
207
208
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_push_amt: days_mode")
209
        unless exists $self->{days_mode};
210
211
    my $dow = $base_date->day_of_week;
212
213
    # Representation fix
214
    # DateTime object dow (1-7) where Monday is 1
215
    # Arrays are 0-based where 0 = Sunday, not 7.
216
    if ( $dow == 7 ) {
217
        $dow = 0;
218
    }
219
220
    return (
221
        # We're using Dayweek useDaysMode option
222
        $self->{days_mode} eq 'Dayweek' &&
223
224
            # It's not a permanently closed day
225
            !$self->{weekly_closed_days}->[$dow]
226
    ) ? 7 : 1;
227
}
228
229
sub is_holiday {
230
    my ( $self, $dt ) = @_;
231
232
    my $localdt = $dt->clone();
233
    my $day     = $localdt->day;
234
    my $month   = $localdt->month;
235
    my $ymd     = $localdt->ymd('');
236
237
    #Change timezone to "floating" before doing any calculations or comparisons
238
    $localdt->set_time_zone("floating");
239
    $localdt->truncate( to => 'day' );
240
241
    return $self->_holidays->{$ymd} if defined( $self->_holidays->{$ymd} );
242
243
    my $dow = $localdt->day_of_week;
244
245
    # Representation fix
246
    # DateTime object dow (1-7) where Monday is 1
247
    # Arrays are 0-based where 0 = Sunday, not 7.
248
    if ( $dow == 7 ) {
249
        $dow = 0;
250
    }
251
252
    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
253
        return 1;
254
    }
255
256
    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
257
        return 1;
258
    }
259
260
    # damn have to go to work after all
261
    return 0;
262
}
263
264
sub next_open_days {
265
    my ( $self, $dt, $to_add ) = @_;
266
267
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->next_open_days: days_mode")
268
        unless exists $self->{days_mode};
269
270
    my $base_date = $dt->clone();
271
272
    $base_date->add( days => $to_add );
273
    my $i = 0;
274
    while ( $self->is_holiday($base_date) && $i < OPEN_DAYS_SEARCH_MAX_ITERATIONS ) {
275
        my $add_next = $self->get_push_amt($base_date);
276
        $base_date->add( days => $add_next );
277
        ++$i;
278
    }
279
280
    if ( $self->is_holiday($base_date) ) {
281
        Koha::Exceptions::Calendar::NoOpenDays->throw(
282
            sprintf( 'Unable to find an open day for library %s', $self->{branchcode} ) );
283
    }
284
285
    return $base_date;
286
}
287
288
sub prev_open_days {
289
    my ( $self, $dt, $to_sub ) = @_;
290
291
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_open_days: days_mode")
292
        unless exists $self->{days_mode};
293
294
    my $base_date = $dt->clone();
295
296
    # It feels logical to be passed a positive number, though we're
297
    # subtracting, so do the right thing
298
    $to_sub = $to_sub > 0 ? 0 - $to_sub : $to_sub;
299
300
    $base_date->add( days => $to_sub );
301
302
    my $i = 0;
303
    while ( $self->is_holiday($base_date) && $i < OPEN_DAYS_SEARCH_MAX_ITERATIONS ) {
304
        my $sub_next = $self->get_push_amt($base_date);
305
306
        # Ensure we're subtracting when we need to be
307
        $sub_next = $sub_next > 0 ? 0 - $sub_next : $sub_next;
308
        $base_date->add( days => $sub_next );
309
        ++$i;
310
    }
311
312
    if ( $self->is_holiday($base_date) ) {
313
        Koha::Exceptions::Calendar::NoOpenDays->throw(
314
            sprintf( 'Unable to find an open day for library %s', $self->{branchcode} ) );
315
    }
316
317
    return $base_date;
318
}
319
320
sub days_forward {
321
    my $self     = shift;
322
    my $start_dt = shift;
323
    my $num_days = shift;
324
325
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->days_forward: days_mode")
326
        unless exists $self->{days_mode};
327
328
    return $start_dt unless $num_days > 0;
329
330
    my $base_dt = $start_dt->clone();
331
332
    while ( $num_days-- ) {
333
        $base_dt = $self->next_open_days( $base_dt, 1 );
334
    }
335
336
    return $base_dt;
337
}
338
339
sub days_between {
340
    my $self     = shift;
341
    my $start_dt = shift;
342
    my $end_dt   = shift;
343
344
    # Change time zone for date math and swap if needed
345
    $start_dt = $start_dt->clone->set_time_zone('floating');
346
    $end_dt   = $end_dt->clone->set_time_zone('floating');
347
    if ( $start_dt->compare($end_dt) > 0 ) {
348
        ( $start_dt, $end_dt ) = ( $end_dt, $start_dt );
349
    }
350
351
    # start and end should not be closed days
352
    my $delta_days = $start_dt->delta_days($end_dt)->delta_days;
353
    while ( $start_dt->compare($end_dt) < 1 ) {
354
        $delta_days-- if $self->is_holiday($start_dt);
355
        $start_dt->add( days => 1 );
356
    }
357
    return DateTime::Duration->new( days => $delta_days );
358
}
359
360
sub hours_between {
361
    my ( $self, $start_date, $end_date ) = @_;
362
    my $start_dt = $start_date->clone()->set_time_zone('floating');
363
    my $end_dt   = $end_date->clone()->set_time_zone('floating');
364
365
    my $duration = $end_dt->delta_ms($start_dt);
366
    $start_dt->truncate( to => 'day' );
367
    $end_dt->truncate( to => 'day' );
368
369
    # NB this is a kludge in that it assumes all days are 24 hours
370
    # However for hourly loans the logic should be expanded to
371
    # take into account open/close times then it would be a duration
372
    # of library open hours
373
    my $skipped_days = 0;
374
    while ( $start_dt->compare($end_dt) < 1 ) {
375
        $skipped_days++ if $self->is_holiday($start_dt);
376
        $start_dt->add( days => 1 );
377
    }
378
379
    if ($skipped_days) {
380
        $duration->subtract_duration( DateTime::Duration->new( hours => 24 * $skipped_days ) );
381
    }
382
383
    return $duration;
384
}
385
386
sub set_daysmode {
387
    my ( $self, $mode ) = @_;
388
389
    # if not testing this is a no op
390
    if ( $self->{test} ) {
391
        $self->{days_mode} = $mode;
392
    }
393
394
    return;
395
}
396
397
sub clear_weekly_closed_days {
398
    my $self = shift;
399
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];    # Sunday only
400
    return;
401
}
402
403
1;
404
__END__
405
406
=head1 NAME
407
408
Koha::Calendar - Object containing a branches calendar
409
410
=head1 SYNOPSIS
411
412
  use Koha::Calendar
413
414
  my $c = Koha::Calendar->new( branchcode => 'MAIN' );
415
  my $dt = dt_from_string();
416
417
  # are we open
418
  $open = $c->is_holiday($dt);
419
  # when will item be due if loan period = $dur (a DateTime::Duration object)
420
  $duedate = $c->addDuration($dt,$dur,'days');
421
422
423
=head1 DESCRIPTION
424
425
  Implements those features of C4::Calendar needed for Staffs Rolling Loans
426
427
=head1 METHODS
428
429
=head2 new : Create a calendar object
430
431
my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
432
433
The option branchcode is required
434
435
436
=head2 addDuration
437
438
    my $dt = $calendar->addDuration($date, $dur, $unit)
439
440
C<$date> is a DateTime object representing the starting date of the interval.
441
442
C<$offset> is a DateTime::Duration to add to it
443
444
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
445
446
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
447
parameter will be removed when issuingrules properly cope with that
448
449
450
=head2 addHours
451
452
    my $dt = $calendar->addHours($date, $dur, $return_by_hour )
453
454
C<$date> is a DateTime object representing the starting date of the interval.
455
456
C<$offset> is a DateTime::Duration to add to it
457
458
C<$return_by_hour> is an integer value representing the opening hour for the branch
459
460
=head2 get_push_amt
461
462
    my $amt = $calendar->get_push_amt($date)
463
464
C<$date> is a DateTime object representing a closed return date
465
466
Using the days_mode syspref value and the nature of the closed return
467
date, return how many days we should jump forward to find another return date
468
469
=head2 addDays
470
471
    my $dt = $calendar->addDays($date, $dur)
472
473
C<$date> is a DateTime object representing the starting date of the interval.
474
475
C<$offset> is a DateTime::Duration to add to it
476
477
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
478
479
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
480
parameter will be removed when issuingrules properly cope with that
481
482
=head2 is_holiday
483
484
$yesno = $calendar->is_holiday($dt);
485
486
passed a DateTime object returns 1 if it is a closed day
487
0 if not according to the calendar
488
489
=head2 days_between
490
491
$duration = $calendar->days_between($start_dt, $end_dt);
492
493
Passed two dates returns a DateTime::Duration object measuring the length between them
494
ignoring closed days. Always returns a positive number irrespective of the
495
relative order of the parameters.
496
497
Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
498
499
=head2 hours_between
500
501
$duration = $calendar->hours_between($start_dt, $end_dt);
502
503
Passed two dates returns a DateTime::Duration object measuring the length between them
504
ignoring closed days. Always returns a positive number irrespective of the
505
relative order of the parameters.
506
507
Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
508
509
=head2 next_open_days
510
511
$datetime = $calendar->next_open_days($duedate_dt, $to_add)
512
513
Passed a Datetime and number of days,  returns another Datetime representing
514
the next open day after adding the passed number of days. It is intended for
515
use to calculate the due date when useDaysMode syspref is set to either
516
'Datedue', 'Calendar' or 'Dayweek'.
517
518
=head2 prev_open_days
519
520
$datetime = $calendar->prev_open_days($duedate_dt, $to_sub)
521
522
Passed a Datetime and a number of days, returns another Datetime
523
representing the previous open day after subtracting the number of passed
524
days. It is intended for use to calculate the due date when useDaysMode
525
syspref is set to either 'Datedue', 'Calendar' or 'Dayweek'.
526
527
=head2 days_forward
528
529
$datetime = $calendar->days_forward($start_dt, $to_add)
530
531
Passed a Datetime and number of days, returns another Datetime representing
532
the next open day after adding the passed number of days. It is intended for
533
use to calculate the due date when useDaysMode syspref is set to either
534
'Datedue', 'Calendar' or 'Dayweek'.
535
536
=head2 set_daysmode
537
538
For testing only allows the calling script to change days mode
539
540
=head2 clear_weekly_closed_days
541
542
In test mode changes the testing set of closed days to a new set with
543
no closed days. TODO passing an array of closed days to this would
544
allow testing of more configurations
545
546
=head2 add_holiday
547
548
Passed a datetime object this will add it to the calendar's list of
549
closed days. This is for testing so that we can alter the Calenfar object's
550
list of specified dates
551
552
=head1 DIAGNOSTICS
553
554
Will croak if not passed a branchcode in new
555
556
=head1 BUGS AND LIMITATIONS
557
558
This only contains a limited subset of the functionality in C4::Calendar
559
Only enough to support Staffs Rolling loans
560
561
=head1 AUTHOR
562
563
Colin Campbell colin.campbell@ptfs-europe.com
564
565
=head1 LICENSE AND COPYRIGHT
566
567
Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
568
569
Koha is free software; you can redistribute it and/or modify it
570
under the terms of the GNU General Public License as published by
571
the Free Software Foundation; either version 3 of the License, or
572
(at your option) any later version.
573
574
Koha is distributed in the hope that it will be useful, but
575
WITHOUT ANY WARRANTY; without even the implied warranty of
576
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
577
GNU General Public License for more details.
578
579
You should have received a copy of the GNU General Public License
580
along with Koha; if not, see <http://www.gnu.org/licenses>.
(-)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 54-62 sub calculate_dropbox_date { Link Here
54
            branchcode   => $branchcode,
54
            branchcode   => $branchcode,
55
        }
55
        }
56
    );
56
    );
57
    my $calendar     = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
57
    my $calendar     = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
58
    my $today        = dt_from_string;
58
    my $today        = dt_from_string;
59
    my $dropbox_date = $calendar->addDuration( $today, -1 );
59
    my $dropbox_date = $calendar->addDuration( $today, -1 );
60
    my $dateInfo = $calendar->get_date_info($dropbox_date);
61
    $dropbox_date = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
60
62
61
    return $dropbox_date;
63
    return $dropbox_date;
62
}
64
}
(-)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 70-76 sub age { Link Here
70
    my $age;
70
    my $age;
71
71
72
    if ($use_calendar) {
72
    if ($use_calendar) {
73
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
73
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
74
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
74
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
75
    } else {
75
    } else {
76
        $age = $today->delta_days( dt_from_string( $self->reservedate ) );
76
        $age = $today->delta_days( dt_from_string( $self->reservedate ) );
Lines 245-251 sub set_waiting { Link Here
245
                branchcode   => $self->branchcode,
245
                branchcode   => $self->branchcode,
246
            }
246
            }
247
        );
247
        );
248
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
248
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode, days_mode => $daysmode });
249
249
250
        $new_expiration_date = $calendar->days_forward( dt_from_string( $self->waitingdate ), $max_pickup_delay );
250
        $new_expiration_date = $calendar->days_forward( dt_from_string( $self->waitingdate ), $max_pickup_delay );
251
    }
251
    }
(-)a/Koha/Patron.pm (-1 / +1 lines)
Lines 1205-1211 sub has_restricting_overdues { Link Here
1205
1205
1206
    my $calendar;
1206
    my $calendar;
1207
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
1207
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
1208
        $calendar = Koha::Calendar->new( branchcode => $params->{issue_branchcode} );
1208
        $calendar = Koha::DiscreteCalendar->new( branchcode => $params->{issue_branchcode} );
1209
    }
1209
    }
1210
1210
1211
    my $dtf    = Koha::Database->new->schema->storage->datetime_parser;
1211
    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 228-233 if ( $op eq 'add_form' ) { Link Here
228
                    my @additional_fields = $library->prepare_cgi_additional_field_values( $input, 'branches' );
228
                    my @additional_fields = $library->prepare_cgi_additional_field_values( $input, 'branches' );
229
                    $library->set_additional_fields( \@additional_fields );
229
                    $library->set_additional_fields( \@additional_fields );
230
230
231
                    Koha::DiscreteCalendar->add_new_branch(undef, $branchcode);
232
231
                    push @messages, { type => 'message', code => 'success_on_insert' };
233
                    push @messages, { type => 'message', code => 'success_on_insert' };
232
                }
234
                }
233
            );
235
            );
Lines 272-277 if ( $op eq 'add_form' ) { Link Here
272
    if ( $@ or not $deleted ) {
274
    if ( $@ or not $deleted ) {
273
        push @messages, { type => 'alert', code => 'error_on_delete' };
275
        push @messages, { type => 'alert', code => 'error_on_delete' };
274
    } else {
276
    } else {
277
        Koha::DiscreteCalendar->delete_branch($branchcode);
275
        push @messages, { type => 'message', code => 'success_on_delete' };
278
        push @messages, { type => 'message', code => 'success_on_delete' };
276
    }
279
    }
277
    $op = 'list';
280
    $op = 'list';
(-)a/circ/returns.pl (-1 lines)
Lines 44-50 use C4::Reserves qw( ModReserve ModReserveAffect CheckReserves ); Link Here
44
use C4::RotatingCollections;
44
use C4::RotatingCollections;
45
use Koha::AuthorisedValues;
45
use Koha::AuthorisedValues;
46
use Koha::BiblioFrameworks;
46
use Koha::BiblioFrameworks;
47
use Koha::Calendar;
48
use Koha::Checkouts;
47
use Koha::Checkouts;
49
use Koha::CirculationRules;
48
use Koha::CirculationRules;
50
use Koha::DateUtils qw( dt_from_string );
49
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 55-61 Link Here
55
        <h5>Additional tools</h5>
55
        <h5>Additional tools</h5>
56
        <ul>
56
        <ul>
57
            [% IF ( CAN_user_tools_edit_calendar ) %]
57
            [% IF ( CAN_user_tools_edit_calendar ) %]
58
                <li><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></li>
58
                <li><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></li>
59
            [% END %]
59
            [% END %]
60
            [% IF ( CAN_user_tools_manage_csv_profiles ) %]
60
            [% IF ( CAN_user_tools_manage_csv_profiles ) %]
61
                <li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
61
                <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/holidays.tt (-686 lines)
Lines 1-686 Link Here
1
[% USE raw %]
2
[% USE Koha %]
3
[% USE Asset %]
4
[% USE KohaDates %]
5
[% USE Branches %]
6
[% PROCESS 'i18n.inc' %]
7
[% SET footerjs = 1 %]
8
[% INCLUDE 'doc-head-open.inc' %]
9
<title
10
    >[% FILTER collapse %]
11
        [% tx("{library} calendar", { library = Branches.GetName( branch ) }) | html %]
12
        &rsaquo; [% t("Tools") | html %] &rsaquo; [% t("Koha") | html %]
13
    [% END %]</title
14
>
15
[% INCLUDE 'doc-head-close.inc' %]
16
[% Asset.css("css/calendar.css") | $raw %]
17
</head>
18
<body id="tools_holidays" class="tools">
19
[% WRAPPER 'header.inc' %]
20
    [% INCLUDE 'cat-search.inc' %]
21
[% END %]
22
23
[% WRAPPER 'sub-header.inc' %]
24
    [% WRAPPER breadcrumbs %]
25
        [% WRAPPER breadcrumb_item %]
26
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
27
        [% END %]
28
        [% WRAPPER breadcrumb_item bc_active= 1 %]
29
            [% tx("{library} calendar", { library = Branches.GetName( branch ) }) | html %]
30
        [% END %]
31
    [% END #/ WRAPPER breadcrumbs %]
32
[% END #/ WRAPPER sub-header.inc %]
33
34
[% WRAPPER 'main-container.inc' aside='tools-menu' %]
35
    <h1>[% tx("{library} calendar", { library = Branches.GetName( branch ) }) | html %]</h1>
36
37
    <div class="row">
38
        <div class="col-sm-6">
39
            <div class="page-section">
40
                <label for="branch">Define the holidays for:</label>
41
                <select id="branch" name="branch">
42
                    [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
43
                </select>
44
45
                <div class="panel" id="showHoliday">
46
                    <form action="/cgi-bin/koha/tools/exceptionHolidays.pl" method="post">
47
                        [% INCLUDE 'csrf-token.inc' %]
48
                        <input type="hidden" name="op" value="cud-edit" />
49
                        <input type="hidden" id="showHolidayType" name="showHolidayType" value="" />
50
                        <fieldset class="brief">
51
                            <h3>Edit this holiday</h3>
52
                            <span id="holtype"></span>
53
                            <ol>
54
                                <li>
55
                                    <strong>Library:</strong> <span id="showBranchNameOutput"></span>
56
                                    <input type="hidden" id="showBranchName" name="showBranchName" />
57
                                </li>
58
                                <li>
59
                                    <strong>From date:</strong>
60
                                    <span id="showDaynameOutput"></span>,
61
                                    [% IF ( dateformat == "us" ) %]
62
                                        <span id="showMonthOutput"></span>/<span id="showDayOutput"></span>/<span id="showYearOutput"></span>
63
                                    [% ELSIF ( dateformat == "metric") %]
64
                                        <span id="showDayOutput"></span>/<span id="showMonthOutput"></span>/<span id="showYearOutput"></span>
65
                                    [% ELSIF ( dateformat == "dmydot") %]
66
                                        <span id="showDayOutput"></span>.<span id="showMonthOutput"></span>.<span id="showYearOutput"></span>
67
                                    [% ELSE %]
68
                                        <span id="showYearOutput"></span>/<span id="showMonthOutput"></span>/<span id="showDayOutput"></span>
69
                                    [% END %]
70
71
                                    <input type="hidden" id="showDayname" name="showDayname" />
72
                                    <input type="hidden" id="showWeekday" name="showWeekday" />
73
                                    <input type="hidden" id="showDay" name="showDay" />
74
                                    <input type="hidden" id="showMonth" name="showMonth" />
75
                                    <input type="hidden" id="showYear" name="showYear" />
76
                                </li>
77
                                <li class="dateinsert">
78
                                    <strong>To date: </strong>
79
                                    <input type="text" id="datecancelrange" name="datecancelrange" size="20" value="[% datecancelrange | html %]" class="flatpickr" />
80
                                </li>
81
                                <li> <label for="showTitle">Title: </label><input type="text" name="showTitle" id="showTitle" size="35" /> </li>
82
                                <!-- showTitle is necessary for exception radio button to work properly -->
83
                                <li>
84
                                    <label for="showDescription">Description:</label>
85
                                    <textarea rows="2" cols="40" id="showDescription" name="showDescription"></textarea>
86
                                </li>
87
                                <li class="radio">
88
                                    <div class="exceptionPossibility" style="position:static">
89
                                        <input type="radio" name="showOperation" id="showOperationExc" value="exception" /> <label for="showOperationExc">Generate an exception for this repeated holiday.</label>
90
                                        <a href="#" class="helptext">[?]</a>
91
                                        <div class="hint">You can make an exception for this holiday rule. This means that you will be able to say that for a repeatable holiday there is one day which is going to be an exception.</div>
92
                                    </div>
93
                                </li>
94
                                <li class="radio">
95
                                    <div class="exceptionPossibility" style="position:static">
96
                                        <input type="radio" name="showOperation" id="showOperationExcRange" value="exceptionrange" />
97
                                        <label for="showOperationExcRange">Generate exceptions on a range of dates.</label>
98
                                        <a href="#" class="helptext">[?]</a>
99
                                        <div class="hint">You can make an exception on a range of dates repeated yearly.</div>
100
                                    </div>
101
                                </li>
102
                                <li class="radio">
103
                                    <input type="radio" name="showOperation" id="showOperationDel" value="cud-delete" />
104
                                    <label for="showOperationDel" id="showOperationDelLabel">Delete this holiday</label>
105
                                    <a href="#" class="helptext">[?]</a>
106
                                    <div class="hint"
107
                                        >This will delete this holiday rule. If it is a repeatable holiday, this option checks for possible exceptions. If an exception exists, this option will remove the exception and set the date to a
108
                                        regular holiday.</div
109
                                    >
110
                                </li>
111
                                <li class="radio">
112
                                    <input type="radio" name="showOperation" id="showOperationDelRange" value="deleterange" /> <label for="showOperationDelRange" id="showOperationDelLabelRange">Delete the single holidays on a range</label>.
113
                                    <a href="#" class="helptext">[?]</a>
114
                                    <div class="hint">This will delete the single holidays rules only. The repeatable holidays and exceptions will not be deleted.</div>
115
                                </li>
116
                                <li class="radio">
117
                                    <input type="radio" name="showOperation" id="showOperationDelRangeRepeat" value="deleterangerepeat" />
118
                                    <label for="showOperationDelRangeRepeat" id="showOperationDelLabelRangeRepeat">Delete the repeated holidays on a range</label>.
119
                                    <a href="#" class="helptext">[?]</a>
120
                                    <div class="hint">This will delete the repeated holidays rules only. The repeatable holidays will be deleted but not the exceptions.</div>
121
                                </li>
122
                                <li class="radio">
123
                                    <input type="radio" name="showOperation" id="showOperationDelRangeRepeatExcept" value="deleterangerepeatexcept" />
124
                                    <label for="showOperationDelRangeRepeatExcept" id="showOperationDelLabelRangeRepeatExcept">Delete the exceptions on a range</label>.
125
                                    <a href="#" class="helptext">[?]</a>
126
                                    <div class="hint">This will delete the exceptions inside a given range. Be careful about your scope range; if it is oversized you could slow down Koha.</div>
127
                                </li>
128
                                <li class="radio">
129
                                    <input type="radio" name="showOperation" id="showOperationEdit" value="cud-edit" checked="checked" /> <label for="showOperationEdit">Edit this holiday</label>
130
                                    <a href="#" class="helptext">[?]</a>
131
                                    <div class="hint"
132
                                        >This will save changes to the holiday's title and description. If the information for a repeatable holiday is modified, it affects all of the dates on which the holiday is repeated.</div
133
                                    ></li
134
                                >
135
                                <li class="checkbox">
136
                                    <input type="checkbox" name="allBranches" id="allBranches" />
137
                                    <label for="allBranches">Copy changes to all libraries</label>.
138
                                    <a href="#" class="helptext">[?]</a>
139
                                    <div class="hint"
140
                                        >If checked, changes for this holiday will be copied to all libraries. If the holiday doesn't exists for a library, holiday is added to calendar. NOTE! This might overwrite existing holidays in other
141
                                        calendars.</div
142
                                    >
143
                                </li>
144
                            </ol>
145
                            <fieldset class="action">
146
                                <input type="submit" name="submit" class="btn btn-primary" value="Save" />
147
                                <a href="#" class="cancel hidePanel showHoliday">Cancel</a>
148
                            </fieldset>
149
                        </fieldset>
150
                        <!-- /.brief -->
151
                    </form>
152
                </div>
153
                <!-- /#showHoliday -->
154
155
                <!-- Panel to deal with new holidays -->
156
                <div class="panel" id="newHoliday">
157
                    <form action="/cgi-bin/koha/tools/newHolidays.pl" method="post">
158
                        [% INCLUDE 'csrf-token.inc' %]
159
                        <input type="hidden" name="op" value="cud-add" />
160
                        <fieldset class="brief">
161
                            <h3>Add new holiday</h3>
162
                            <ol>
163
                                <li>
164
                                    <strong>Library:</strong>
165
                                    <span id="newBranchNameOutput"></span>
166
                                    <input type="hidden" id="newBranchName" name="newBranchName" />
167
                                </li>
168
                                <li>
169
                                    <strong>From date:</strong>
170
                                    <span id="newDaynameOutput"></span>,
171
                                    [% IF ( dateformat == "us" ) %]
172
                                        <span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>
173
                                    [% ELSIF ( dateformat == "metric" ) %]
174
                                        <span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>
175
                                    [% ELSIF ( dateformat == "dmydot" ) %]
176
                                        <span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>
177
                                    [% ELSE %]
178
                                        <span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>
179
                                    [% END %]
180
181
                                    <input type="hidden" id="newDayname" name="showDayname" />
182
                                    <input type="hidden" id="newWeekday" name="newWeekday" />
183
                                    <input type="hidden" id="newDay" name="newDay" />
184
                                    <input type="hidden" id="newMonth" name="newMonth" />
185
                                    <input type="hidden" id="newYear" name="newYear" />
186
                                </li>
187
                                <li class="dateinsert">
188
                                    <strong>To date: </strong>
189
                                    <input type="text" id="dateofrange" name="dateofrange" size="20" value="[% dateofrange | html %]" class="flatpickr" />
190
                                    <div class="hint">This field only applies when holidays are added on a range.</div>
191
                                </li>
192
                                <li>
193
                                    <label for="title">Title: </label>
194
                                    <input type="text" name="newTitle" id="title" size="35"
195
                                /></li>
196
                                <li>
197
                                    <label for="newDescription">Description:</label>
198
                                    <textarea rows="2" cols="40" id="newDescription" name="newDescription"></textarea>
199
                                </li>
200
                                <li class="radio">
201
                                    <input type="radio" name="newOperation" id="newOperationOnce" value="holiday" checked="checked" />
202
                                    <label for="newOperationOnce">Holiday only on this day</label>.
203
                                    <a href="#" class="helptext">[?]</a>
204
                                    <div class="hint">Make a single holiday. For example, selecting August 1, 2012 will make it a holiday, but will not affect August 1 in other years.</div>
205
                                </li>
206
                                <li class="radio">
207
                                    <input type="radio" name="newOperation" id="newOperationDay" value="weekday" />
208
                                    <label for="newOperationDay">Holiday repeated every same day of the week</label>.
209
                                    <a href="#" class="helptext">[?]</a>
210
                                    <div class="hint">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.</div>
211
                                </li>
212
                                <li class="radio">
213
                                    <input type="radio" name="newOperation" id="newOperationYear" value="repeatable" />
214
                                    <label for="newOperationYear">Holiday repeated yearly on the same date</label>.
215
                                    <a href="#" class="helptext">[?]</a>
216
                                    <div class="hint"
217
                                        >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
218
                                        year.</div
219
                                    >
220
                                </li>
221
                                <li class="radio">
222
                                    <input type="radio" name="newOperation" id="newOperationField" value="holidayrange" />
223
                                    <label for="newOperationField">Holidays on a range</label>.
224
                                    <a href="#" class="helptext">[?]</a>
225
                                    <div class="hint"
226
                                        >Make a single holiday on a range. For example, selecting August 1, 2012 and August 10, 2012 will make all days between August 1 and 10 a holiday, but will not affect August 1-10 in other years.</div
227
                                    >
228
                                </li>
229
                                <li class="radio">
230
                                    <input type="radio" name="newOperation" id="newOperationFieldyear" value="holidayrangerepeat" />
231
                                    <label for="newOperationFieldyear">Holidays repeated yearly on a range</label>.
232
                                    <a href="#" class="helptext">[?]</a>
233
                                    <div class="hint"
234
                                        >Make a single holiday on a range repeated yearly. For example, selecting August 1, 2012 and August 10, 2012 will make all days between August 1 and 10 a holiday, and will affect August 1-10 in other
235
                                        years.</div
236
                                    >
237
                                </li>
238
                                <li class="checkbox">
239
                                    <input type="checkbox" name="allBranches" id="allBranches" />
240
                                    <label for="allBranches">Copy to all libraries</label>.
241
                                    <a href="#" class="helptext">[?]</a>
242
                                    <div class="hint">If checked, this holiday will be copied to all libraries. If the holiday already exists for a library, no change is made.</div>
243
                                </li>
244
                            </ol>
245
                            <fieldset class="action">
246
                                <input type="submit" name="submit" class="btn btn-primary" value="Save" />
247
                                <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
248
                            </fieldset>
249
                        </fieldset>
250
                        <!-- /.brief -->
251
                    </form>
252
                </div>
253
                <!-- /#newHoliday -->
254
255
                <div id="calendar-container">
256
                    <h3>Calendar information</h3>
257
                    <span id="calendar-anchor"></span>
258
                </div>
259
                <div style="margin-top: 2em;">
260
                    <form action="copy-holidays.pl" method="post">
261
                        [% INCLUDE 'csrf-token.inc' %]
262
                        <input type="hidden" name="op" value="cud-copy" />
263
                        <input type="hidden" name="from_branchcode" value="[% branch | html %]" />
264
                        <label for="branchcode">Copy holidays to:</label>
265
                        <select id="branchcode" name="branchcode">
266
                            <option value=""></option>
267
                            [% FOREACH l IN Branches.all() %]
268
                                <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
269
                            [% END %]
270
                        </select>
271
                        <input type="submit" class="btn btn-primary" value="Copy" />
272
                    </form>
273
                </div>
274
            </div>
275
            <!-- /.page-section -->
276
        </div>
277
        <!-- /.col-sm-6 -->
278
279
        <div class="col-sm-6">
280
            <div class="page-section">
281
                <div class="help">
282
                    <h4>Hints</h4>
283
                    <ul>
284
                        <li>Search in the calendar the day you want to set as holiday.</li>
285
                        <li>Click the date to add or edit a holiday.</li>
286
                        <li>Enter a title and description for the holiday.</li>
287
                        <li>Specify how the holiday should repeat.</li>
288
                        <li>Click Save to finish.</li>
289
                    </ul>
290
                    <h4>Key</h4>
291
                    <p>
292
                        <span class="key normalday">Working day</span>
293
                        <span class="key holiday">Unique holiday</span>
294
                        <span class="key repeatableweekly">Holiday repeating weekly</span>
295
                        <span class="key repeatableyearly">Holiday repeating yearly</span>
296
                        <span class="key exception">Holiday exception</span>
297
                    </p>
298
                </div>
299
                <!-- /#help -->
300
301
                <div id="holiday-list">
302
                    <!-- Exceptions First -->
303
                    <!--   this will probably always have the least amount of data -->
304
                    [% IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
305
                        <h3>Exceptions</h3>
306
                        <label class="controls">
307
                            <input type="checkbox" name="show_past" id="show_past_holidayexceptions" class="show_past" />
308
                            Show past entries
309
                        </label>
310
                        <table id="holidayexceptions">
311
                            <thead>
312
                                <tr>
313
                                    <th class="exception">Date</th>
314
                                    <th class="exception">Title</th>
315
                                    <th class="exception">Description</th>
316
                                </tr>
317
                            </thead>
318
                            <tbody>
319
                                [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
320
                                    <tr data-date="[% EXCEPTION_HOLIDAYS_LOO.DATE_SORT | html %]">
321
                                        <td data-order="[% EXCEPTION_HOLIDAYS_LOO.DATE_SORT | html %]">
322
                                            <a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% EXCEPTION_HOLIDAYS_LOO.DATE | uri %]"> [% EXCEPTION_HOLIDAYS_LOO.DATE | html %] </a>
323
                                        </td>
324
                                        <td>[% EXCEPTION_HOLIDAYS_LOO.TITLE | html %]</td>
325
                                        <td>[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
326
                                    </tr>
327
                                [% END %]
328
                            </tbody>
329
                        </table>
330
                        <!-- /#holidayexceptions -->
331
                    [% END # /IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
332
333
                    [% IF ( WEEK_DAYS_LOOP ) %]
334
                        <h3>Weekly - Repeatable holidays</h3>
335
                        <table id="holidayweeklyrepeatable">
336
                            <thead>
337
                                <tr>
338
                                    <th class="repeatableweekly">Day of week</th>
339
                                    <th class="repeatableweekly">Title</th>
340
                                    <th class="repeatableweekly">Description</th>
341
                                </tr>
342
                            </thead>
343
                            <tbody>
344
                                [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
345
                                    <tr>
346
                                        <td>[% WEEK_DAYS_LOO.KEY | html %]</td>
347
                                        <td>[% WEEK_DAYS_LOO.TITLE | html %]</td>
348
                                        <td>[% WEEK_DAYS_LOO.DESCRIPTION | html %]</td>
349
                                    </tr>
350
                                [% END %]
351
                            </tbody>
352
                        </table>
353
                        <!-- /#holidayweeklyrepeatable -->
354
                    [% END # / IF ( WEEK_DAYS_LOOP ) %]
355
356
                    [% IF ( DAY_MONTH_HOLIDAYS_LOOP ) %]
357
                        <h3>Yearly - Repeatable holidays</h3>
358
                        <table id="holidaysyearlyrepeatable">
359
                            <thead>
360
                                <tr>
361
                                    [% IF ( dateformat == "metric" ) %]
362
                                        <th class="repeatableyearly">Day/month</th>
363
                                    [% ELSE %]
364
                                        <th class="repeatableyearly">Month/day</th>
365
                                    [% END %]
366
                                    <th class="repeatableyearly">Title</th>
367
                                    <th class="repeatableyearly">Description</th>
368
                                </tr>
369
                            </thead>
370
                            <tbody>
371
                                [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
372
                                    <tr>
373
                                        <td data-order="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]"> [% DAY_MONTH_HOLIDAYS_LOO.DATE | html %] </td>
374
                                        <td>[% DAY_MONTH_HOLIDAYS_LOO.TITLE | html %]</td>
375
                                        <td>[% DAY_MONTH_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
376
                                    </tr>
377
                                [% END %]
378
                            </tbody>
379
                        </table>
380
                        <!-- /#holidaysyearlyrepeatable -->
381
                    [% END # /IF ( DAY_MONTH_HOLIDAYS_LOOP ) %]
382
383
                    [% IF ( HOLIDAYS_LOOP ) %]
384
                        <h3>Unique holidays</h3>
385
                        <label class="controls">
386
                            <input type="checkbox" name="show_past" id="show_past_holidaysunique" class="show_past" />
387
                            Show past entries
388
                        </label>
389
                        <table id="holidaysunique">
390
                            <thead>
391
                                <tr>
392
                                    <th class="holiday">Date</th>
393
                                    <th class="holiday">Title</th>
394
                                    <th class="holiday">Description</th>
395
                                </tr>
396
                            </thead>
397
                            <tbody>
398
                                [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
399
                                    <tr data-date="[% HOLIDAYS_LOO.DATE_SORT | html %]">
400
                                        <td data-order="[% HOLIDAYS_LOO.DATE_SORT | html %]">
401
                                            <a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% HOLIDAYS_LOO.DATE | uri %]"> [% HOLIDAYS_LOO.DATE | html %] </a>
402
                                        </td>
403
                                        <td>[% HOLIDAYS_LOO.TITLE | html %]</td>
404
                                        <td>[% HOLIDAYS_LOO.DESCRIPTION.replace('\\\r\\\n', '<br />') | html %]</td>
405
                                    </tr>
406
                                [% END %]
407
                            </tbody>
408
                        </table>
409
                        <!-- #holidaysunique -->
410
                    [% END # /IF ( HOLIDAYS_LOOP ) %]
411
                </div>
412
                <!-- /#holiday-list -->
413
            </div>
414
            <!-- /.page-section -->
415
        </div>
416
        <!-- /.col-sm-6 -->
417
    </div>
418
    <!-- /.row -->
419
[% END %]
420
[% MACRO jsinclude BLOCK %]
421
    [% INCLUDE 'calendar.inc' %]
422
    [% INCLUDE 'datatables.inc' %]
423
    [% Asset.js("js/tools-menu.js") | $raw %]
424
    <script>
425
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
426
427
        /* Creates all the structures to deal with all different kinds of holidays */
428
        var week_days = new Array();
429
        var holidays = new Array();
430
        var holidates = new Array();
431
        var exception_holidays = new Array();
432
        var day_month_holidays = new Array();
433
        var hola= "[% code | html %]";
434
        [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
435
            week_days["[% WEEK_DAYS_LOO.KEY | html %]"] = {title:"[% WEEK_DAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% WEEK_DAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
436
        [% END %]
437
        [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
438
            holidates.push("[% HOLIDAYS_LOO.KEY | html %]");
439
            holidays["[% HOLIDAYS_LOO.KEY | html %]"] = {title:"[% HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
440
        [% END %]
441
        [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
442
            exception_holidays["[% EXCEPTION_HOLIDAYS_LOO.KEY | html %]"] = {title:"[% EXCEPTION_HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
443
        [% END %]
444
        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
445
            day_month_holidays["[% DAY_MONTH_HOLIDAYS_LOO.KEY | html %]"] = {title:"[% DAY_MONTH_HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% DAY_MONTH_HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
446
        [% END %]
447
448
        function holidayOperation(formObject, opType) {
449
            var op = document.getElementsByName('operation');
450
            op[0].value = opType;
451
            formObject.submit();
452
        }
453
454
        // This function shows the "Show Holiday" panel //
455
        function showHoliday (exceptionPossibility, dayName, day, month, year, weekDay, title, description, holidayType) {
456
            $("#newHoliday").slideUp("fast");
457
            $("#showHoliday").slideDown("fast");
458
            $('#showDaynameOutput').html(dayName);
459
            $('#showDayname').val(dayName);
460
            $('#showBranchNameOutput').html($("#branch :selected").text());
461
            $('#showBranchName').val($("#branch").val());
462
            $('#showDayOutput').html(day);
463
            $('#showDay').val(day);
464
            $('#showMonthOutput').html(month);
465
            $('#showMonth').val(month);
466
            $('#showYearOutput').html(year);
467
            $('#showYear').val(year);
468
            $('#showDescription').val(description);
469
            $('#showWeekday:first').val(weekDay);
470
            $('#showTitle').val(title);
471
            $('#showHolidayType').val(holidayType);
472
473
            if (holidayType == 'exception') {
474
                $("#showOperationDelLabel").html(_("Delete this exception."));
475
                $("#holtype").attr("class","key exception").html(_("Holiday exception"));
476
            } else if(holidayType == 'weekday') {
477
                $("#showOperationDelLabel").html(_("Delete this holiday."));
478
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
479
            } else if(holidayType == 'daymonth') {
480
                $("#showOperationDelLabel").html(_("Delete this holiday."));
481
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
482
            } else {
483
                $("#showOperationDelLabel").html(_("Delete this holiday."));
484
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
485
            }
486
487
            if (exceptionPossibility == 1) {
488
                $(".exceptionPossibility").parent().show();
489
            } else {
490
                $(".exceptionPossibility").parent().hide();
491
            }
492
        }
493
494
        // This function shows the "Add Holiday" panel //
495
        function newHoliday (dayName, day, month, year, weekDay) {
496
            $("#showHoliday").slideUp("fast");
497
            $("#newHoliday").slideDown("fast");
498
            $("#newDaynameOutput").html(dayName);
499
            $("#newDayname").val(dayName);
500
            $("#newBranchNameOutput").html($('#branch :selected').text());
501
            $("#newBranchName").val($('#branch').val());
502
            $("#newDayOutput").html(day);
503
            $("#newDay").val(day);
504
            $("#newMonthOutput").html(month);
505
            $("#newMonth").val(month);
506
            $("#newYearOutput").html(year);
507
            $("#newYear").val(year);
508
            $("#newWeekday:first").val(weekDay);
509
        }
510
511
        function hidePanel(aPanelName) {
512
            $("#"+aPanelName).slideUp("fast");
513
        }
514
515
        function changeBranch () {
516
            var branch = $("#branch option:selected").val();
517
            location.href='/cgi-bin/koha/tools/holidays.pl?branch=' + branch + '&calendardate=' + "[% calendardate | html %]";
518
        }
519
520
        /**
521
        * Build settings to be passed to the formatDay function for each day in the calendar
522
        * @param  {object} dayElem - HTML node passed from Flatpickr
523
        * @return {void}
524
        */
525
        function dateStatusHandler( dayElem ) {
526
            var day = dayElem.dateObj.getDate();
527
            var month = dayElem.dateObj.getMonth() + 1;
528
            var year = dayElem.dateObj.getFullYear();
529
            var weekDay = dayElem.dateObj.getDay();
530
            var dayMonth = month + '/' + day;
531
            var dateString = year + '/' + month + '/' + day;
532
            if (exception_holidays[dateString] != null) {
533
                formatDay( [ "exception", _("Exception: %s").format(exception_holidays[dateString].title)], dayElem );
534
            } else if ( week_days[weekDay] != null ){
535
                formatDay( [ "repeatableweekly", _("Weekly holiday: %s").format(week_days[weekDay].title)], dayElem );
536
            } else if ( day_month_holidays[dayMonth] != null ) {
537
                formatDay( [ "repeatableyearly", _("Yearly holiday: %s").format(day_month_holidays[dayMonth].title)], dayElem );
538
            } else if (holidays[dateString] != null) {
539
                formatDay( [ "holiday", _("Single holiday: %s").format(holidays[dateString].title)], dayElem );
540
            } else {
541
                formatDay( [ "normalday", _("Normal day")], dayElem );
542
            }
543
        }
544
545
        /**
546
        * Adds style and title attribute to a day on the calendar
547
        * @param  {object} settings - span class attribute ([0]) and title attribute ([1])
548
        * @param  {node}   dayElem  - HTML node passed from Flatpickr
549
        * @return {void}
550
        */
551
        function formatDay( settings, dayElem ){
552
            $(dayElem).attr("title", settings[1]).addClass( settings[0]);
553
        }
554
555
        /**
556
        * Triggers an action based on a click on a calendar day: If a holiday exists on
557
        * that day it loads an edit form. If there is no existing holiday one can be created
558
        * @param  {object} calendar - a Date object corresponding to the clicked day
559
        * @return {void}
560
        */
561
        function dateChanged( calendar ) {
562
            var day = calendar.getDate();
563
            var month = calendar.getMonth() + 1;
564
            var year = calendar.getFullYear();
565
            var weekDay = calendar.getDay();
566
            var dayName = weekdays[weekDay];
567
            var dayMonth = month + '/' + day;
568
            var dateString = year + '/' + month + '/' + day;
569
            if (holidays[dateString] != null) {
570
                showHoliday(0, dayName, day, month, year, weekDay, holidays[dateString].title,     holidays[dateString].description, 'ymd');
571
            } else if (exception_holidays[dateString] != null) {
572
                showHoliday(0, dayName, day, month, year, weekDay, exception_holidays[dateString].title, exception_holidays[dateString].description, 'exception');
573
            } else if (week_days[weekDay] != null) {
574
                showHoliday(1, dayName, day, month, year, weekDay, week_days[weekDay].title,     week_days[weekDay].description, 'weekday');
575
            } else if (day_month_holidays[dayMonth] != null) {
576
                showHoliday(1, dayName, day, month, year, weekDay, day_month_holidays[dayMonth].title, day_month_holidays[dayMonth].description, 'daymonth');
577
            } else {
578
                newHoliday(dayName, day, month, year, weekDay);
579
            }
580
        };
581
582
        /* Custom table search configuration: If a table row
583
            has an "expired" class, hide it UNLESS the
584
            show_expired checkbox is checked */
585
        $.fn.dataTable.ext.search.push(
586
            function( settings, searchData, index, rowData, counter ) {
587
                var table = settings.nTable.id;
588
                var row = $(settings.aoData[index].nTr);
589
                if( row.hasClass("date_past") && !$("#show_past_" + table ).prop("checked") ){
590
                    return false;
591
                } else {
592
                    return true;
593
                }
594
            }
595
        );
596
597
        // Create current date variable
598
        var date = new Date();
599
        var datestring = date.toISOString().substring(0, 10);
600
601
        $(document).ready(function() {
602
603
            $(".helptext + .hint").hide();
604
            $("#branch").change(function(){
605
                changeBranch();
606
            });
607
            $("#holidayweeklyrepeatable>tbody>tr").each(function(){
608
                var first_td = $(this).find('td').first();
609
                first_td.html(weekdays[first_td.html()]);
610
            });
611
            $("#holidayweeklyrepeatable").kohaTable({
612
                dom: "t",
613
                paginate: false,
614
            });
615
616
            let dt_params = {
617
                dom: "t",
618
                paginate: false,
619
                createdRow: function (row, data, dataIndex) {
620
                    var holiday = $(row).data("date");
621
                    if (holiday < datestring) {
622
                        $(row).addClass("date_past");
623
                    }
624
                },
625
            };
626
            $("#holidayexceptions").kohaTable(dt_params);
627
            $("#holidaysyearlyrepeatable").kohaTable(dt_params);
628
            $("#holidaysunique").kohaTable(dt_params);
629
630
            $(".show_past").on("change", function(){
631
                tables.draw();
632
            });
633
634
            $("a.helptext").click(function(){
635
                $(this).parent().find(".helptext + .hint").toggle(); return false;
636
            });
637
638
            const dateofrange = document.querySelector("#dateofrange")._flatpickr;
639
            const datecancelrange = document.querySelector("#datecancelrange")._flatpickr;
640
641
            $("#dateofrange").each(function () { this.value = "" });
642
            $("#datecancelrange").each(function () { this.value = "" });
643
644
            var maincalendar = $("#calendar-anchor").flatpickr({
645
                inline: true,
646
                onReady: function( selectedDates, dateStr, instance ){
647
                    // We do not want to display the 'close' icon in this case
648
                    $(instance.input).siblings('.flatpickr-input').hide();
649
                },
650
                onDayCreate: function( dObj, dStr, fp, dayElem ){
651
                    /* for each day on the calendar, get the
652
                      correct status information for the date */
653
                    dateStatusHandler( dayElem );
654
                },
655
                onChange: function( selectedDates, dateStr, instance ){
656
                    var fromdate = selectedDates[0];
657
                    var enddate = dateofrange.selectedDates[0];
658
659
                    dateChanged( fromdate );
660
661
                    dateofrange.set( 'defaultDate', fromdate );
662
                    dateofrange.set( 'minDate', fromdate );
663
664
                    if ( enddate != undefined ) {
665
                        if ( enddate < fromdate ) {
666
                            dateofrange.set("defaultDate", fromdate);
667
                            dateofrange.setDate(fromdate);
668
                        }
669
                    }
670
671
                },
672
                defaultDate: new Date("[% keydate | html %]")
673
            });
674
675
            $(".hidePanel").on("click",function(){
676
                if( $(this).hasClass("showHoliday") ){
677
                    hidePanel("showHoliday");
678
                } else {
679
                    hidePanel("newHoliday");
680
                }
681
            })
682
        });
683
    </script>
684
[% END %]
685
686
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +1 lines)
Lines 117-123 Link Here
117
            [% END %]
117
            [% END %]
118
            <dl>
118
            <dl>
119
                [% IF ( CAN_user_tools_edit_calendar ) %]
119
                [% IF ( CAN_user_tools_edit_calendar ) %]
120
                    <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
120
                    <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
121
                    <dd>Define days when the library is closed</dd>
121
                    <dd>Define days when the library is closed</dd>
122
                [% END %]
122
                [% END %]
123
123
(-)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 243-249 foreach my $branchcode (@branchcodes) { #BEGIN BRANCH LOOP Link Here
243
    # If respecting calendar get the correct waiting since date
243
    # If respecting calendar get the correct waiting since date
244
    my $waiting_date;
244
    my $waiting_date;
245
    if ($use_calendar) {
245
    if ($use_calendar) {
246
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => 'Calendar' );
246
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => 'Calendar' });
247
        my $duration = DateTime::Duration->new( days => -$days );
247
        my $duration = DateTime::Duration->new( days => -$days );
248
        $waiting_date = $calendar->addDays( $date_to_run, $duration );    #Add negative of days
248
        $waiting_date = $calendar->addDays( $date_to_run, $duration );    #Add negative of days
249
    } else {
249
    } else {
(-)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 455-461 my %seen = map { $_ => 1 } @branches; Link Here
455
foreach my $branchcode (@branches) {
455
foreach my $branchcode (@branches) {
456
    my $calendar;
456
    my $calendar;
457
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
457
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
458
        $calendar = Koha::Calendar->new( branchcode => $branchcode );
458
        $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
459
        if ( $calendar->is_holiday($date_to_run) ) {
459
        if ( $calendar->is_holiday($date_to_run) ) {
460
            next;
460
            next;
461
        }
461
        }
(-)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/copy-holidays.pl (-42 lines)
Lines 1-42 Link Here
1
#!/usr/bin/perl
2
3
# Copyright 2012 Catalyst IT
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use CGI qw ( -utf8 );
23
24
use C4::Auth qw( checkauth );
25
use C4::Output;
26
27
use C4::Calendar;
28
29
my $input = CGI->new;
30
my $op    = $input->param('op') // q{};
31
my $dbh   = C4::Context->dbh();
32
33
checkauth( $input, 0, { tools => 'edit_calendar' }, 'intranet' );
34
35
my $branchcode      = $input->param('branchcode');
36
my $from_branchcode = $input->param('from_branchcode');
37
38
if ( $op eq 'cud-copy' ) {
39
    C4::Calendar->new( branchcode => $from_branchcode )->copy_to_branch($branchcode) if $from_branchcode && $branchcode;
40
}
41
42
print $input->redirect( "/cgi-bin/koha/tools/holidays.pl?branch=" . ( $branchcode || $from_branchcode ) );
(-)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 (-206 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
}
(-)a/tools/holidays.pl (-144 lines)
Lines 1-144 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 C4::Calendar;
27
use Koha::DateUtils qw( dt_from_string output_pref );
28
29
my $input = CGI->new;
30
31
my $dbh = C4::Context->dbh();
32
33
# Get the template to use
34
my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
35
    {
36
        template_name => "tools/holidays.tt",
37
        type          => "intranet",
38
        query         => $input,
39
        flagsrequired => { tools => 'edit_calendar' },
40
    }
41
);
42
43
# calendardate - date passed in url for human readability (syspref)
44
# if the url has an invalid date default to 'now.'
45
# FIXME There is something to improve in the date handling here
46
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate') ); } || dt_from_string;
47
my $calendardate     = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
48
49
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
50
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
51
$keydate =~ s/-/\//g;
52
53
my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
54
55
# Get all the holidays
56
57
my $calendar           = C4::Calendar->new( branchcode => $branch );
58
my $week_days_holidays = $calendar->get_week_days_holidays();
59
my @week_days;
60
foreach my $weekday ( keys %$week_days_holidays ) {
61
62
    # warn "WEEK DAY : $weekday";
63
    my %week_day;
64
    %week_day = (
65
        KEY         => $weekday,
66
        TITLE       => $week_days_holidays->{$weekday}{title},
67
        DESCRIPTION => $week_days_holidays->{$weekday}{description}
68
    );
69
    push @week_days, \%week_day;
70
}
71
72
my $day_month_holidays = $calendar->get_day_month_holidays();
73
my @day_month_holidays;
74
foreach my $monthDay ( keys %$day_month_holidays ) {
75
76
    # Determine date format on month and day.
77
    my $day_monthdate;
78
    my $day_monthdate_sort;
79
    if ( C4::Context->preference("dateformat") eq "metric" ) {
80
        $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
81
        $day_monthdate      = "$day_month_holidays->{$monthDay}{day}/$day_month_holidays->{$monthDay}{month}";
82
    } elsif ( C4::Context->preference("dateformat") eq "dmydot" ) {
83
        $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}.$day_month_holidays->{$monthDay}{day}";
84
        $day_monthdate      = "$day_month_holidays->{$monthDay}{day}.$day_month_holidays->{$monthDay}{month}";
85
    } elsif ( C4::Context->preference("dateformat") eq "us" ) {
86
        $day_monthdate      = "$day_month_holidays->{$monthDay}{month}/$day_month_holidays->{$monthDay}{day}";
87
        $day_monthdate_sort = $day_monthdate;
88
    } else {
89
        $day_monthdate      = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
90
        $day_monthdate_sort = $day_monthdate;
91
    }
92
    my %day_month;
93
    %day_month = (
94
        KEY         => $monthDay,
95
        DATE_SORT   => $day_monthdate_sort,
96
        DATE        => $day_monthdate,
97
        TITLE       => $day_month_holidays->{$monthDay}{title},
98
        DESCRIPTION => $day_month_holidays->{$monthDay}{description}
99
    );
100
    push @day_month_holidays, \%day_month;
101
}
102
103
my $exception_holidays = $calendar->get_exception_holidays();
104
my @exception_holidays;
105
foreach my $yearMonthDay ( keys %$exception_holidays ) {
106
    my $exceptiondate = eval { dt_from_string( $exception_holidays->{$yearMonthDay}{date} ) };
107
    my %exception_holiday;
108
    %exception_holiday = (
109
        KEY         => $yearMonthDay,
110
        DATE_SORT   => $exception_holidays->{$yearMonthDay}{date},
111
        DATE        => output_pref( { dt => $exceptiondate, dateonly => 1 } ),
112
        TITLE       => $exception_holidays->{$yearMonthDay}{title},
113
        DESCRIPTION => $exception_holidays->{$yearMonthDay}{description}
114
    );
115
    push @exception_holidays, \%exception_holiday;
116
}
117
118
my $single_holidays = $calendar->get_single_holidays();
119
my @holidays;
120
foreach my $yearMonthDay ( keys %$single_holidays ) {
121
    my $holidaydate_dt = eval { dt_from_string( $single_holidays->{$yearMonthDay}{date} ) };
122
    my %holiday;
123
    %holiday = (
124
        KEY         => $yearMonthDay,
125
        DATE_SORT   => $single_holidays->{$yearMonthDay}{date},
126
        DATE        => output_pref( { dt => $holidaydate_dt, dateonly => 1 } ),
127
        TITLE       => $single_holidays->{$yearMonthDay}{title},
128
        DESCRIPTION => $single_holidays->{$yearMonthDay}{description}
129
    );
130
    push @holidays, \%holiday;
131
}
132
133
$template->param(
134
    WEEK_DAYS_LOOP          => \@week_days,
135
    HOLIDAYS_LOOP           => \@holidays,
136
    EXCEPTION_HOLIDAYS_LOOP => \@exception_holidays,
137
    DAY_MONTH_HOLIDAYS_LOOP => \@day_month_holidays,
138
    calendardate            => $calendardate,
139
    keydate                 => $keydate,
140
    branch                  => $branch,
141
);
142
143
# Shows the template with the real values replaced
144
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/newHolidays.pl (-158 lines)
Lines 1-157 Link Here
1
#!/usr/bin/perl
2
#FIXME: perltidy this file
3
4
# This file is part of Koha.
5
#
6
# Koha is free software; you can redistribute it and/or modify it
7
# under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# Koha is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public Lic# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
19
use Modern::Perl;
20
21
use CGI qw ( -utf8 );
22
23
use C4::Auth qw( checkauth );
24
use C4::Output;
25
26
use C4::Calendar;
27
use DateTime;
28
use Koha::DateUtils qw( dt_from_string output_pref );
29
30
my $input = CGI->new;
31
my $op    = $input->param('op') // q{};
32
my $dbh   = C4::Context->dbh();
33
34
checkauth( $input, 0, { tools => 'edit_calendar' }, 'intranet' );
35
36
our $branchcode = $input->param('newBranchName');
37
my $originalbranchcode = $branchcode;
38
our $weekday = $input->param('newWeekday');
39
our $day     = $input->param('newDay');
40
our $month   = $input->param('newMonth');
41
our $year    = $input->param('newYear');
42
my $dateofrange = $input->param('dateofrange');
43
our $title        = $input->param('newTitle');
44
our $description  = $input->param('newDescription');
45
our $newoperation = $input->param('newOperation');
46
my $allbranches = $input->param('allBranches');
47
48
my $first_dt = DateTime->new( year => $year, month => $month, day => $day );
49
my $end_dt   = eval { dt_from_string($dateofrange); };
50
51
my $calendardate = output_pref( { dt => $first_dt, dateonly => 1, dateformat => 'iso' } );
52
53
$title || ( $title = '' );
54
if ($description) {
55
    $description =~ s/\r/\\r/g;
56
    $description =~ s/\n/\\n/g;
57
} else {
58
    $description = '';
59
}
60
61
# We make an array with holiday's days
62
our @holiday_list = ();
63
if ($end_dt) {
64
    for ( my $dt = $first_dt->clone() ; $dt <= $end_dt ; $dt->add( days => 1 ) ) {
65
        push @holiday_list, $dt->clone();
66
    }
67
}
68
69
if ( $op eq 'cud-add' && $allbranches ) {
70
    my $libraries = Koha::Libraries->search;
71
    while ( my $library = $libraries->next ) {
72
        add_holiday( $newoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description );
73
    }
74
} elsif ( $op eq 'cud-add' ) {
75
    add_holiday( $newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description );
76
}
77
78
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
79
80
#FIXME: move add_holiday() to a better place
81
sub add_holiday {
82
    ( $newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description ) = @_;
83
    my $calendar = C4::Calendar->new( branchcode => $branchcode );
84
85
    if ( $newoperation eq 'weekday' ) {
86
        unless ( $weekday && ( $weekday ne '' ) ) {
87
88
            # was dow calculated by javascript?  original code implies it was supposed to be.
89
            # if not, we need it.
90
            $weekday = &Date::Calc::Day_of_Week( $year, $month, $day ) % 7 unless ($weekday);
91
        }
92
        unless ( $calendar->isHoliday( $day, $month, $year ) ) {
93
            $calendar->insert_week_day_holiday(
94
                weekday     => $weekday,
95
                title       => $title,
96
                description => $description
97
            );
98
        }
99
    } elsif ( $newoperation eq 'repeatable' ) {
100
        unless ( $calendar->isHoliday( $day, $month, $year ) ) {
101
            $calendar->insert_day_month_holiday(
102
                day         => $day,
103
                month       => $month,
104
                title       => $title,
105
                description => $description
106
            );
107
        }
108
    } elsif ( $newoperation eq 'holiday' ) {
109
        unless ( $calendar->isHoliday( $day, $month, $year ) ) {
110
            $calendar->insert_single_holiday(
111
                day         => $day,
112
                month       => $month,
113
                year        => $year,
114
                title       => $title,
115
                description => $description
116
            );
117
        }
118
119
    } elsif ( $newoperation eq 'holidayrange' ) {
120
        if (@holiday_list) {
121
            foreach my $date (@holiday_list) {
122
                unless (
123
                    $calendar->isHoliday(
124
                        $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year}
125
                    )
126
                    )
127
                {
128
                    $calendar->insert_single_holiday(
129
                        day         => $date->{local_c}->{day},
130
                        month       => $date->{local_c}->{month},
131
                        year        => $date->{local_c}->{year},
132
                        title       => $title,
133
                        description => $description
134
                    );
135
                }
136
            }
137
        }
138
    } elsif ( $newoperation eq 'holidayrangerepeat' ) {
139
        if (@holiday_list) {
140
            foreach my $date (@holiday_list) {
141
                unless (
142
                    $calendar->isHoliday(
143
                        $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year}
144
                    )
145
                    )
146
                {
147
                    $calendar->insert_day_month_holiday(
148
                        day         => $date->{local_c}->{day},
149
                        month       => $date->{local_c}->{month},
150
                        title       => $title,
151
                        description => $description
152
                    );
153
                }
154
            }
155
        }
156
    }
157
}
158
- 

Return to bug 17015