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

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

Return to bug 17015