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

(-)a/C4/Calendar.pm (-736 lines)
Lines 1-736 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
608
=head2 delete_exception_holiday_range
609
610
    delete_exception_holiday_range(weekday => $weekday
611
                   day => $day,
612
                   month => $month,
613
                   year => $year);
614
615
Delete a holiday for $self->{branchcode}.
616
617
C<$day> Is the day month to make the date to delete.
618
619
C<$month> Is month to make the date to delete.
620
621
C<$year> Is year to make the date to delete.
622
623
=cut
624
625
sub delete_exception_holiday_range {
626
    my $self = shift;
627
    my %options = @_;
628
629
    my $dbh = C4::Context->dbh();
630
    my $sth = $dbh->prepare("DELETE FROM special_holidays WHERE (branchcode = ?) AND (isexception = 1) AND (day = ?) AND (month = ?) AND (year = ?)");
631
    $sth->execute($self->{branchcode}, $options{day}, $options{month}, $options{year});
632
633
    # changed the 'single_holidays' table, lets force/reset its cache
634
    my $cache = Koha::Caches->get_instance();
635
    my $key   = $self->{branchcode} . "_holidays";
636
    $cache->clear_from_cache($key);
637
}
638
639
=head2 isHoliday
640
641
    $isHoliday = isHoliday($day, $month $year);
642
643
C<$day> Is the day to check whether if is a holiday or not.
644
645
C<$month> Is the month to check whether if is a holiday or not.
646
647
C<$year> Is the year to check whether if is a holiday or not.
648
649
=cut
650
651
sub isHoliday {
652
    my ($self, $day, $month, $year) = @_;
653
	# FIXME - date strings are stored in non-padded metric format. should change to iso.
654
	$month=$month+0;
655
	$year=$year+0;
656
	$day=$day+0;
657
    my $weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7;
658
    my $weekDays   = $self->get_week_days_holidays();
659
    my $dayMonths  = $self->get_day_month_holidays();
660
    my $exceptions = $self->get_exception_holidays();
661
    my $singles    = $self->get_single_holidays();
662
    if (defined($exceptions->{"$year/$month/$day"})) {
663
        return 0;
664
    } else {
665
        if ((exists($weekDays->{$weekday})) ||
666
            (exists($dayMonths->{"$month/$day"})) ||
667
            (exists($singles->{"$year/$month/$day"}))) {
668
            return 1;
669
        } else {
670
            return 0;
671
        }
672
    }
673
674
}
675
676
=head2 copy_to_branch
677
678
    $calendar->copy_to_branch($target_branch)
679
680
=cut
681
682
sub copy_to_branch {
683
    my ($self, $target_branch) = @_;
684
685
    croak "No target_branch" unless $target_branch;
686
687
    my $target_calendar = C4::Calendar->new(branchcode => $target_branch);
688
689
    my ($y, $m, $d) = Today();
690
    my $today = sprintf ISO_DATE_FORMAT, $y,$m,$d;
691
692
    my $wdh = $self->get_week_days_holidays;
693
    my $target_wdh = $target_calendar->get_week_days_holidays;
694
    foreach my $key (keys %$wdh) {
695
        unless (grep { $_ eq $key } keys %$target_wdh) {
696
            $target_calendar->insert_week_day_holiday( weekday => $key, %{ $wdh->{$key} } )
697
        }
698
    }
699
700
    my $dmh = $self->get_day_month_holidays;
701
    my $target_dmh = $target_calendar->get_day_month_holidays;
702
    foreach my $values (values %$dmh) {
703
        unless (grep { $_->{day} eq $values->{day} && $_->{month} eq $values->{month} } values %$target_dmh) {
704
            $target_calendar->insert_day_month_holiday(%{ $values });
705
        }
706
    }
707
708
    my $exception_holidays = $self->get_exception_holidays;
709
    my $target_exceptions = $target_calendar->get_exception_holidays;
710
    foreach my $values ( grep {$_->{date} gt $today} values %{ $exception_holidays }) {
711
        unless ( grep { $_->{date} eq $values->{date} } values %$target_exceptions) {
712
            $target_calendar->insert_exception_holiday(%{ $values });
713
        }
714
    }
715
716
    my $single_holidays = $self->get_single_holidays;
717
    my $target_singles = $target_calendar->get_single_holidays;
718
    foreach my $values ( grep {$_->{date} gt $today} values %{ $single_holidays }) {
719
        unless ( grep { $_->{date} eq $values->{date} } values %$target_singles){
720
            $target_calendar->insert_single_holiday(%{ $values });
721
        }
722
    }
723
724
    return 1;
725
}
726
727
1;
728
729
__END__
730
731
=head1 AUTHOR
732
733
Koha Physics Library UNLP <matias_veleda@hotmail.com>
734
735
=cut
736
(-)a/C4/Circulation.pm (-8 / +7 lines)
Lines 43-49 use Koha::AuthorisedValues; Link Here
43
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
43
use Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue;
44
use Koha::Biblioitems;
44
use Koha::Biblioitems;
45
use Koha::DateUtils qw( dt_from_string );
45
use Koha::DateUtils qw( dt_from_string );
46
use Koha::Calendar;
46
use Koha::DiscreteCalendar;
47
use Koha::Checkouts;
47
use Koha::Checkouts;
48
use Koha::Illrequests;
48
use Koha::Illrequests;
49
use Koha::Items;
49
use Koha::Items;
Lines 1417-1423 sub checkHighHolds { Link Here
1417
                branchcode   => $branchcode,
1417
                branchcode   => $branchcode,
1418
            }
1418
            }
1419
        );
1419
        );
1420
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
1420
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
1421
1421
1422
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron->unblessed );
1422
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $patron->unblessed );
1423
1423
Lines 2642-2651 sub _calculate_new_debar_dt { Link Here
2642
        my $new_debar_dt;
2642
        my $new_debar_dt;
2643
        # Use the calendar or not to calculate the debarment date
2643
        # Use the calendar or not to calculate the debarment date
2644
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2644
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2645
            my $calendar = Koha::Calendar->new(
2645
            my $calendar = Koha::DiscreteCalendar->new({
2646
                branchcode => $branchcode,
2646
                branchcode => $branchcode,
2647
                days_mode  => 'Calendar'
2647
                days_mode  => 'Calendar'
2648
            );
2648
            });
2649
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2649
            $new_debar_dt = $calendar->addDuration( $return_date, $suspension_days );
2650
        }
2650
        }
2651
        else {
2651
        else {
Lines 3720-3726 sub CalcDateDue { Link Here
3720
        else { # days
3720
        else { # days
3721
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3721
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3722
        }
3722
        }
3723
        my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
3723
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
3724
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3724
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3725
        if ($loanlength->{lengthunit} eq 'days') {
3725
        if ($loanlength->{lengthunit} eq 'days') {
3726
            $datedue->set_hour(23);
3726
            $datedue->set_hour(23);
Lines 3759-3772 sub CalcDateDue { Link Here
3759
            }
3759
            }
3760
        }
3760
        }
3761
        if ( $daysmode ne 'Days' ) {
3761
        if ( $daysmode ne 'Days' ) {
3762
          my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
3762
          my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch, days_mode => $daysmode });
3763
          if ( $calendar->is_holiday($datedue) ) {
3763
          if ( $calendar->is_holiday($datedue) ) {
3764
              # Don't return on a closed day
3764
              # Don't return on a closed day
3765
              $datedue = $calendar->prev_open_days( $datedue, 1 );
3765
              $datedue = $calendar->prev_open_day( $datedue )->set(hour => 23, minute => 59);
3766
          }
3766
          }
3767
        }
3767
        }
3768
    }
3768
    }
3769
3770
    return $datedue;
3769
    return $datedue;
3771
}
3770
}
3772
3771
(-)a/C4/HoldsQueue.pm (-1 / +10 lines)
Lines 31-36 use Koha::Libraries; Link Here
31
31
32
use List::Util qw( shuffle );
32
use List::Util qw( shuffle );
33
use List::MoreUtils qw( any );
33
use List::MoreUtils qw( any );
34
use Koha::DiscreteCalendar;
35
use Data::Dumper;
34
36
35
our (@ISA, @EXPORT_OK);
37
our (@ISA, @EXPORT_OK);
36
BEGIN {
38
BEGIN {
Lines 76-81 sub TransportCostMatrix { Link Here
76
            cost             => $cost,
78
            cost             => $cost,
77
            disable_transfer => $disabled
79
            disable_transfer => $disabled
78
        };
80
        };
81
82
        if ( !my $ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
83
            my $calendars->{$from} ||= Koha::DiscreteCalendar->new({ branchcode => $from });
84
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
85
              $calendars->{$from}->is_holiday( $today );
86
        }
87
79
    }
88
    }
80
89
81
    return \%transport_cost_matrix;
90
    return \%transport_cost_matrix;
Lines 832-838 sub load_branches_to_pull_from { Link Here
832
    my $today = dt_from_string();
841
    my $today = dt_from_string();
833
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
842
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
834
        @branches_to_use = grep {
843
        @branches_to_use = grep {
835
            !Koha::Calendar->new( branchcode => $_ )
844
            !Koha::DiscreteCalendar->new({ branchcode => $_ })
836
              ->is_holiday( $today )
845
              ->is_holiday( $today )
837
        } @branches_to_use;
846
        } @branches_to_use;
838
    }
847
    }
(-)a/C4/Overdues.pm (-2 / +5 lines)
Lines 29-34 use Carp qw( carp ); Link Here
29
29
30
use C4::Accounts;
30
use C4::Accounts;
31
use C4::Context;
31
use C4::Context;
32
use C4::Log; # logaction
33
use Koha::DateUtils;
34
use Koha::DiscreteCalendar;
32
use Koha::Account::Lines;
35
use Koha::Account::Lines;
33
use Koha::Account::Offsets;
36
use Koha::Account::Offsets;
34
use Koha::DateUtils qw( output_pref );
37
use Koha::DateUtils qw( output_pref );
Lines 320-326 sub get_chargeable_units { Link Here
320
    my $charge_duration;
323
    my $charge_duration;
321
    if ($unit eq 'hours') {
324
    if ($unit eq 'hours') {
322
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
325
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
323
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
326
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
324
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
327
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
325
        } else {
328
        } else {
326
            $charge_duration = $date_returned->delta_ms( $date_due );
329
            $charge_duration = $date_returned->delta_ms( $date_due );
Lines 332-338 sub get_chargeable_units { Link Here
332
    }
335
    }
333
    else { # days
336
    else { # days
334
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
337
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
335
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
338
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
336
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
339
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
337
        } else {
340
        } else {
338
            $charge_duration = $date_returned->delta_days( $date_due );
341
            $charge_duration = $date_returned->delta_days( $date_due );
(-)a/C4/Reserves.pm (-2 / +3 lines)
Lines 35-44 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::CirculationRules;
38
use Koha::CirculationRules;
40
use Koha::Database;
39
use Koha::Database;
41
use Koha::DateUtils qw( dt_from_string output_pref );
40
use Koha::DateUtils qw( dt_from_string output_pref );
41
use Koha::DiscreteCalendar;
42
use Koha::Hold;
42
use Koha::Holds;
43
use Koha::Holds;
43
use Koha::ItemTypes;
44
use Koha::ItemTypes;
44
use Koha::Items;
45
use Koha::Items;
Lines 1016-1022 sub CancelExpiredReserves { Link Here
1016
    my $holds = Koha::Holds->search( $params );
1017
    my $holds = Koha::Holds->search( $params );
1017
1018
1018
    while ( my $hold = $holds->next ) {
1019
    while ( my $hold = $holds->next ) {
1019
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
1020
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
1020
1021
1021
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
1022
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
1022
1023
(-)a/Koha/Calendar.pm (-555 lines)
Lines 1-555 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
12
sub new {
13
    my ( $classname, %options ) = @_;
14
    my $self = {};
15
    bless $self, $classname;
16
    for my $o_name ( keys %options ) {
17
        my $o = lc $o_name;
18
        $self->{$o} = $options{$o_name};
19
    }
20
    if ( !defined $self->{branchcode} ) {
21
        croak 'No branchcode argument passed to Koha::Calendar->new';
22
    }
23
    $self->_init();
24
    return $self;
25
}
26
27
sub _init {
28
    my $self       = shift;
29
    my $branch     = $self->{branchcode};
30
    my $dbh        = C4::Context->dbh();
31
    my $weekly_closed_days_sth = $dbh->prepare(
32
'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL'
33
    );
34
    $weekly_closed_days_sth->execute( $branch );
35
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
36
    while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) {
37
        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
38
    }
39
    my $day_month_closed_days_sth = $dbh->prepare(
40
'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL'
41
    );
42
    $day_month_closed_days_sth->execute( $branch );
43
    $self->{day_month_closed_days} = {};
44
    while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) {
45
        $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } =
46
          1;
47
    }
48
49
    $self->{test}            = 0;
50
    return;
51
}
52
53
sub _holidays {
54
    my ($self) = @_;
55
56
    my $key      = $self->{branchcode} . "_holidays";
57
    my $cache    = Koha::Caches->get_instance();
58
    my $holidays = $cache->get_from_cache($key);
59
60
    # $holidays looks like:
61
    # {
62
    #    20131122 => 1, # single_holiday
63
    #    20131123 => 0, # exception_holiday
64
    #    ...
65
    # }
66
67
    # Populate the cache if necessary
68
    unless ($holidays) {
69
        my $dbh = C4::Context->dbh;
70
        $holidays = {};
71
72
        # Add holidays for each branch
73
        my $holidays_sth = $dbh->prepare(
74
'SELECT day, month, year, MAX(isexception) FROM special_holidays WHERE branchcode = ? GROUP BY day, month, year'
75
        );
76
        $holidays_sth->execute($self->{branchcode});
77
78
        while ( my ( $day, $month, $year, $exception ) =
79
            $holidays_sth->fetchrow )
80
        {
81
            my $datestring =
82
                sprintf( "%04d", $year )
83
              . sprintf( "%02d", $month )
84
              . sprintf( "%02d", $day );
85
86
            $holidays->{$datestring} = $exception ? 0 : 1;
87
        }
88
        $cache->set_in_cache( $key, $holidays, { expiry => 76800 } );
89
    }
90
    return $holidays // {};
91
}
92
93
sub addDuration {
94
    my ( $self, $startdate, $add_duration, $unit ) = @_;
95
96
97
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDuration: days_mode")
98
        unless exists $self->{days_mode};
99
100
    # Default to days duration (legacy support I guess)
101
    if ( ref $add_duration ne 'DateTime::Duration' ) {
102
        $add_duration = DateTime::Duration->new( days => $add_duration );
103
    }
104
105
    $unit ||= 'days'; # default days ?
106
    my $dt;
107
    if ( $unit eq 'hours' ) {
108
        # Fixed for legacy support. Should be set as a branch parameter
109
        my $return_by_hour = 10;
110
111
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
112
    } else {
113
        # days
114
        $dt = $self->addDays($startdate, $add_duration);
115
    }
116
    return $dt;
117
}
118
119
sub addHours {
120
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
121
    my $base_date = $startdate->clone();
122
123
    $base_date->add_duration($hours_duration);
124
125
    # If we are using the calendar behave for now as if Datedue
126
    # was the chosen option (current intended behaviour)
127
128
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addHours: days_mode")
129
        unless exists $self->{days_mode};
130
131
    if ( $self->{days_mode} ne 'Days' &&
132
          $self->is_holiday($base_date) ) {
133
134
        if ( $hours_duration->is_negative() ) {
135
            $base_date = $self->prev_open_days($base_date, 1);
136
        } else {
137
            $base_date = $self->next_open_days($base_date, 1);
138
        }
139
140
        $base_date->set_hour($return_by_hour);
141
142
    }
143
144
    return $base_date;
145
}
146
147
sub addDays {
148
    my ( $self, $startdate, $days_duration ) = @_;
149
    my $base_date = $startdate->clone();
150
151
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->addDays: days_mode")
152
        unless exists $self->{days_mode};
153
154
    if ( $self->{days_mode} eq 'Calendar' ) {
155
        # use the calendar to skip all days the library is closed
156
        # when adding
157
        my $days = abs $days_duration->in_units('days');
158
159
        if ( $days_duration->is_negative() ) {
160
            while ($days) {
161
                $base_date = $self->prev_open_days($base_date, 1);
162
                --$days;
163
            }
164
        } else {
165
            while ($days) {
166
                $base_date = $self->next_open_days($base_date, 1);
167
                --$days;
168
            }
169
        }
170
171
    } else { # Days, Datedue or Dayweek
172
        # use straight days, then use calendar to push
173
        # the date to the next open day as appropriate
174
        # if Datedue or Dayweek
175
        $base_date->add_duration($days_duration);
176
177
        if ( $self->{days_mode} eq 'Datedue' ||
178
            $self->{days_mode} eq 'Dayweek') {
179
            # Datedue or Dayweek, then use the calendar to push
180
            # the date to the next open day if holiday
181
            if ( $self->is_holiday($base_date) ) {
182
                my $dow = $base_date->day_of_week;
183
                my $days = $days_duration->in_units('days');
184
                # Is it a period based on weeks
185
                my $push_amt = $days % 7 == 0 ?
186
                    $self->get_push_amt($base_date) : 1;
187
                if ( $days_duration->is_negative() ) {
188
                    $base_date = $self->prev_open_days($base_date, $push_amt);
189
                } else {
190
                    $base_date = $self->next_open_days($base_date, $push_amt);
191
                }
192
            }
193
        }
194
    }
195
196
    return $base_date;
197
}
198
199
sub get_push_amt {
200
    my ( $self, $base_date) = @_;
201
202
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_push_amt: days_mode")
203
        unless exists $self->{days_mode};
204
205
    my $dow = $base_date->day_of_week;
206
    # Representation fix
207
    # DateTime object dow (1-7) where Monday is 1
208
    # Arrays are 0-based where 0 = Sunday, not 7.
209
    if ( $dow == 7 ) {
210
        $dow = 0;
211
    }
212
213
    return (
214
        # We're using Dayweek useDaysMode option
215
        $self->{days_mode} eq 'Dayweek' &&
216
        # It's not a permanently closed day
217
        !$self->{weekly_closed_days}->[$dow]
218
    ) ? 7 : 1;
219
}
220
221
sub is_holiday {
222
    my ( $self, $dt ) = @_;
223
224
    my $localdt = $dt->clone();
225
    my $day   = $localdt->day;
226
    my $month = $localdt->month;
227
    my $ymd   = $localdt->ymd('');
228
229
    #Change timezone to "floating" before doing any calculations or comparisons
230
    $localdt->set_time_zone("floating");
231
    $localdt->truncate( to => 'day' );
232
233
    return $self->_holidays->{$ymd} if defined($self->_holidays->{$ymd});
234
235
    my $dow = $localdt->day_of_week;
236
    # Representation fix
237
    # DateTime object dow (1-7) where Monday is 1
238
    # Arrays are 0-based where 0 = Sunday, not 7.
239
    if ( $dow == 7 ) {
240
        $dow = 0;
241
    }
242
243
    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
244
        return 1;
245
    }
246
247
    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
248
        return 1;
249
    }
250
251
    # damn have to go to work after all
252
    return 0;
253
}
254
255
sub next_open_days {
256
    my ( $self, $dt, $to_add ) = @_;
257
258
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->next_open_days: days_mode")
259
        unless exists $self->{days_mode};
260
261
    my $base_date = $dt->clone();
262
263
    $base_date->add(days => $to_add);
264
    while ($self->is_holiday($base_date)) {
265
        my $add_next = $self->get_push_amt($base_date);
266
        $base_date->add(days => $add_next);
267
    }
268
    return $base_date;
269
}
270
271
sub prev_open_days {
272
    my ( $self, $dt, $to_sub ) = @_;
273
274
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->get_open_days: days_mode")
275
        unless exists $self->{days_mode};
276
277
    my $base_date = $dt->clone();
278
279
    # It feels logical to be passed a positive number, though we're
280
    # subtracting, so do the right thing
281
    $to_sub = $to_sub > 0 ? 0 - $to_sub : $to_sub;
282
283
    $base_date->add(days => $to_sub);
284
285
    while ($self->is_holiday($base_date)) {
286
        my $sub_next = $self->get_push_amt($base_date);
287
        # Ensure we're subtracting when we need to be
288
        $sub_next = $sub_next > 0 ? 0 - $sub_next : $sub_next;
289
        $base_date->add(days => $sub_next);
290
    }
291
292
    return $base_date;
293
}
294
295
sub days_forward {
296
    my $self     = shift;
297
    my $start_dt = shift;
298
    my $num_days = shift;
299
300
    Koha::Exceptions::MissingParameter->throw("Missing mandatory option for Koha:Calendar->days_forward: days_mode")
301
        unless exists $self->{days_mode};
302
303
    return $start_dt unless $num_days > 0;
304
305
    my $base_dt = $start_dt->clone();
306
307
    while ($num_days--) {
308
        $base_dt = $self->next_open_days($base_dt, 1);
309
    }
310
311
    return $base_dt;
312
}
313
314
sub days_between {
315
    my $self     = shift;
316
    my $start_dt = shift;
317
    my $end_dt   = shift;
318
319
    # Change time zone for date math and swap if needed
320
    $start_dt = $start_dt->clone->set_time_zone('floating');
321
    $end_dt = $end_dt->clone->set_time_zone('floating');
322
    if( $start_dt->compare($end_dt) > 0 ) {
323
        ( $start_dt, $end_dt ) = ( $end_dt, $start_dt );
324
    }
325
326
    # start and end should not be closed days
327
    my $delta_days = $start_dt->delta_days($end_dt)->delta_days;
328
    while( $start_dt->compare($end_dt) < 1 ) {
329
        $delta_days-- if $self->is_holiday($start_dt);
330
        $start_dt->add( days => 1 );
331
    }
332
    return DateTime::Duration->new( days => $delta_days );
333
}
334
335
sub hours_between {
336
    my ($self, $start_date, $end_date) = @_;
337
    my $start_dt = $start_date->clone()->set_time_zone('floating');
338
    my $end_dt = $end_date->clone()->set_time_zone('floating');
339
340
    my $duration = $end_dt->delta_ms($start_dt);
341
    $start_dt->truncate( to => 'day' );
342
    $end_dt->truncate( to => 'day' );
343
344
    # NB this is a kludge in that it assumes all days are 24 hours
345
    # However for hourly loans the logic should be expanded to
346
    # take into account open/close times then it would be a duration
347
    # of library open hours
348
    my $skipped_days = 0;
349
    while( $start_dt->compare($end_dt) < 1 ) {
350
        $skipped_days++ if $self->is_holiday($start_dt);
351
        $start_dt->add( days => 1 );
352
    }
353
354
    if ($skipped_days) {
355
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
356
    }
357
358
    return $duration;
359
}
360
361
sub set_daysmode {
362
    my ( $self, $mode ) = @_;
363
364
    # if not testing this is a no op
365
    if ( $self->{test} ) {
366
        $self->{days_mode} = $mode;
367
    }
368
369
    return;
370
}
371
372
sub clear_weekly_closed_days {
373
    my $self = shift;
374
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];    # Sunday only
375
    return;
376
}
377
378
1;
379
__END__
380
381
=head1 NAME
382
383
Koha::Calendar - Object containing a branches calendar
384
385
=head1 SYNOPSIS
386
387
  use Koha::Calendar
388
389
  my $c = Koha::Calendar->new( branchcode => 'MAIN' );
390
  my $dt = dt_from_string();
391
392
  # are we open
393
  $open = $c->is_holiday($dt);
394
  # when will item be due if loan period = $dur (a DateTime::Duration object)
395
  $duedate = $c->addDuration($dt,$dur,'days');
396
397
398
=head1 DESCRIPTION
399
400
  Implements those features of C4::Calendar needed for Staffs Rolling Loans
401
402
=head1 METHODS
403
404
=head2 new : Create a calendar object
405
406
my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
407
408
The option branchcode is required
409
410
411
=head2 addDuration
412
413
    my $dt = $calendar->addDuration($date, $dur, $unit)
414
415
C<$date> is a DateTime object representing the starting date of the interval.
416
417
C<$offset> is a DateTime::Duration to add to it
418
419
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
420
421
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
422
parameter will be removed when issuingrules properly cope with that
423
424
425
=head2 addHours
426
427
    my $dt = $calendar->addHours($date, $dur, $return_by_hour )
428
429
C<$date> is a DateTime object representing the starting date of the interval.
430
431
C<$offset> is a DateTime::Duration to add to it
432
433
C<$return_by_hour> is an integer value representing the opening hour for the branch
434
435
=head2 get_push_amt
436
437
    my $amt = $calendar->get_push_amt($date)
438
439
C<$date> is a DateTime object representing a closed return date
440
441
Using the days_mode syspref value and the nature of the closed return
442
date, return how many days we should jump forward to find another return date
443
444
=head2 addDays
445
446
    my $dt = $calendar->addDays($date, $dur)
447
448
C<$date> is a DateTime object representing the starting date of the interval.
449
450
C<$offset> is a DateTime::Duration to add to it
451
452
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
453
454
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
455
parameter will be removed when issuingrules properly cope with that
456
457
=head2 is_holiday
458
459
$yesno = $calendar->is_holiday($dt);
460
461
passed a DateTime object returns 1 if it is a closed day
462
0 if not according to the calendar
463
464
=head2 days_between
465
466
$duration = $calendar->days_between($start_dt, $end_dt);
467
468
Passed two dates returns a DateTime::Duration object measuring the length between them
469
ignoring closed days. Always returns a positive number irrespective of the
470
relative order of the parameters.
471
472
Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
473
474
=head2 hours_between
475
476
$duration = $calendar->hours_between($start_dt, $end_dt);
477
478
Passed two dates returns a DateTime::Duration object measuring the length between them
479
ignoring closed days. Always returns a positive number irrespective of the
480
relative order of the parameters.
481
482
Note: This routine assumes neither the passed start_dt nor end_dt can be a closed day
483
484
=head2 next_open_days
485
486
$datetime = $calendar->next_open_days($duedate_dt, $to_add)
487
488
Passed a Datetime and number of days,  returns another Datetime representing
489
the next open day after adding the passed number of days. It is intended for
490
use to calculate the due date when useDaysMode syspref is set to either
491
'Datedue', 'Calendar' or 'Dayweek'.
492
493
=head2 prev_open_days
494
495
$datetime = $calendar->prev_open_days($duedate_dt, $to_sub)
496
497
Passed a Datetime and a number of days, returns another Datetime
498
representing the previous open day after subtracting the number of passed
499
days. It is intended for use to calculate the due date when useDaysMode
500
syspref is set to either 'Datedue', 'Calendar' or 'Dayweek'.
501
502
=head2 days_forward
503
504
$datetime = $calendar->days_forward($start_dt, $to_add)
505
506
Passed a Datetime and number of days, returns another Datetime representing
507
the next open day after adding the passed number of days. It is intended for
508
use to calculate the due date when useDaysMode syspref is set to either
509
'Datedue', 'Calendar' or 'Dayweek'.
510
511
=head2 set_daysmode
512
513
For testing only allows the calling script to change days mode
514
515
=head2 clear_weekly_closed_days
516
517
In test mode changes the testing set of closed days to a new set with
518
no closed days. TODO passing an array of closed days to this would
519
allow testing of more configurations
520
521
=head2 add_holiday
522
523
Passed a datetime object this will add it to the calendar's list of
524
closed days. This is for testing so that we can alter the Calenfar object's
525
list of specified dates
526
527
=head1 DIAGNOSTICS
528
529
Will croak if not passed a branchcode in new
530
531
=head1 BUGS AND LIMITATIONS
532
533
This only contains a limited subset of the functionality in C4::Calendar
534
Only enough to support Staffs Rolling loans
535
536
=head1 AUTHOR
537
538
Colin Campbell colin.campbell@ptfs-europe.com
539
540
=head1 LICENSE AND COPYRIGHT
541
542
Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
543
544
Koha is free software; you can redistribute it and/or modify it
545
under the terms of the GNU General Public License as published by
546
the Free Software Foundation; either version 3 of the License, or
547
(at your option) any later version.
548
549
Koha is distributed in the hope that it will be useful, but
550
WITHOUT ANY WARRANTY; without even the implied warranty of
551
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
552
GNU General Public License for more details.
553
554
You should have received a copy of the GNU General Public License
555
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 55-63 sub calculate_dropbox_date { Link Here
55
            branchcode   => $branchcode,
55
            branchcode   => $branchcode,
56
        }
56
        }
57
    );
57
    );
58
    my $calendar     = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
58
    my $calendar     = Koha::DiscreteCalendar->new({ branchcode => $branchcode, days_mode => $daysmode });
59
    my $today        = dt_from_string;
59
    my $today        = dt_from_string;
60
    my $dropbox_date = $calendar->addDuration( $today, -1 );
60
    my $dropbox_date = $calendar->addDuration( $today, -1 );
61
    my $dateInfo = $calendar->get_date_info($dropbox_date);
62
    $dropbox_date = dt_from_string($dateInfo->{date} ." ". $dateInfo->{close_hour}, 'iso', C4::Context->tz());
61
63
62
    return $dropbox_date;
64
    return $dropbox_date;
63
}
65
}
(-)a/Koha/CurbsidePickup.pm (-2 / +2 lines)
Lines 26-32 use base qw(Koha::Object); Link Here
26
use C4::Circulation qw( CanBookBeIssued AddIssue );
26
use C4::Circulation qw( CanBookBeIssued AddIssue );
27
use C4::Members::Messaging qw( GetMessagingPreferences );
27
use C4::Members::Messaging qw( GetMessagingPreferences );
28
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
28
use C4::Letters qw( GetPreparedLetter EnqueueLetter );
29
use Koha::Calendar;
29
use Koha::DiscreteCalendar;
30
use Koha::DateUtils qw( dt_from_string );
30
use Koha::DateUtils qw( dt_from_string );
31
use Koha::Patron;
31
use Koha::Patron;
32
use Koha::Library;
32
use Koha::Library;
Lines 56-62 sub new { Link Here
56
    Koha::Exceptions::CurbsidePickup::NotEnabled->throw
56
    Koha::Exceptions::CurbsidePickup::NotEnabled->throw
57
      unless $policy && $policy->enabled;
57
      unless $policy && $policy->enabled;
58
58
59
    my $calendar = Koha::Calendar->new( branchcode => $params->{branchcode} );
59
    my $calendar = Koha::DiscreteCalendar->new( branchcode => $params->{branchcode} );
60
    Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw
60
    Koha::Exceptions::CurbsidePickup::LibraryIsClosed->throw
61
      if $calendar->is_holiday( $params->{scheduled_pickup_datetime} );
61
      if $calendar->is_holiday( $params->{scheduled_pickup_datetime} );
62
62
(-)a/Koha/DiscreteCalendar.pm (+1372 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/]
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
        };
154
    }
155
156
    return @datesInfos;
157
}
158
159
=head2 add_new_branch
160
161
    Koha::DiscreteCalendar->add_new_branch($copyBranch, $newBranch)
162
163
This method will copy everything from a given branch to a new branch
164
C<$copyBranch> is the branch to copy from
165
C<$newBranch> is the branch to be created, and copy into
166
167
=cut
168
169
sub add_new_branch {
170
    my ( $classname, $copyBranch, $newBranch) = @_;
171
172
    my $schema = Koha::Database->new->schema;
173
174
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
175
            branchcode => $copyBranch
176
    });
177
178
    unless ($branch_rs->count) {
179
        $copyBranch = Koha::Libraries->next->branchcode;
180
        $branch_rs = $schema->resultset('DiscreteCalendar')->search({
181
            branchcode => $copyBranch
182
        });
183
    }
184
185
    $schema->{AutoCommit} = 0;
186
    $schema->storage->txn_begin;
187
188
    while (my $row = $branch_rs->next()) {
189
        $schema->resultset('DiscreteCalendar')->create({
190
            date         => $row->date(),
191
            branchcode   => $newBranch,
192
            is_opened    => $row->is_opened(),
193
            holiday_type => $row->holiday_type(),
194
            open_hour    => $row->open_hour(),
195
            close_hour   => $row->close_hour(),
196
        });
197
    }
198
199
    eval { $schema->storage->txn_commit; };
200
201
    if ($@) {
202
        $schema->storage->rollback;
203
    }
204
205
    $schema->{AutoCommit} = 1;
206
}
207
208
=head2 delete_branch
209
210
    Koha::DiscreteCalendar->delete_branch($branchcode)
211
212
This method will delete every discrete_calendar entry for a given branch
213
C<$branchcode> is the code of the branch we want to remove from the table
214
215
=cut
216
217
sub delete_branch {
218
    my ( $classname, $branchcode ) = @_;
219
220
    my $schema = Koha::Database->new->schema;
221
222
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
223
            branchcode => $branchcode
224
    });
225
226
    if ($branch_rs->count) {
227
        $branch_rs->delete;
228
    }
229
}
230
231
232
=head2 get_date_info
233
234
    my $date = $calendar->get_date_info;
235
236
Returns a reference-to-hash representing a DiscreteCalendar date data object.
237
The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>,
238
C<$open_hour>, C<$close_hour> and C<$note>.
239
240
=cut
241
242
sub get_date_info {
243
    my ($self, $date) = @_;
244
    my $branchcode = $self->{branchcode};
245
    my $schema = Koha::Database->new->schema;
246
    my $dtf = $schema->storage->datetime_parser;
247
    #String dates for Database usage
248
    my $date_string = $dtf->format_datetime($date);
249
250
    my $rs = $schema->resultset('DiscreteCalendar')->search(
251
        {
252
            branchcode => $branchcode,
253
        },
254
        {
255
            select  => [ 'date', { DATE => 'date' } ],
256
            as      => [qw/ date date /],
257
            where   => \['DATE(?) = date', $date_string ],
258
            columns =>[ qw/ branchcode holiday_type open_hour close_hour note/]
259
        },
260
    );
261
    my $dateDTO;
262
    while (my $date = $rs->next()) {
263
        $dateDTO = {
264
            date         => $date->date(),
265
            branchcode   => $date->branchcode(),
266
            holiday_type => $date->holiday_type() ,
267
            open_hour    => $date->open_hour(),
268
            close_hour   => $date->close_hour(),
269
            note         => $date->note()
270
        };
271
    }
272
273
    return $dateDTO;
274
}
275
276
=head2 get_max_date
277
278
    my $maxDate = $calendar->get_max_date();
279
280
Returns the furthest date available in the database of current branch.
281
282
=cut
283
284
sub get_max_date {
285
    my $self = shift;
286
    my $branchcode = $self->{branchcode};
287
    my $schema = Koha::Database->new->schema;
288
289
    my $rs = $schema->resultset('DiscreteCalendar')->search(
290
        {
291
            branchcode => $branchcode
292
        },
293
        {
294
            select => [{ MAX => 'date' } ],
295
            as     => [qw/ max /],
296
        }
297
    );
298
299
    return $rs->next()->get_column('max');
300
}
301
302
=head2 get_min_date
303
304
    my $minDate = $calendar->get_min_date();
305
306
Returns the oldest date available in the database of current branch.
307
308
=cut
309
310
sub get_min_date {
311
    my $self = shift;
312
    my $branchcode = $self->{branchcode};
313
    my $schema = Koha::Database->new->schema;
314
315
    my $rs = $schema->resultset('DiscreteCalendar')->search(
316
        {
317
            branchcode => $branchcode
318
        },
319
        {
320
            select => [{ MIN => 'date' } ],
321
            as     => [qw/ min /],
322
        }
323
    );
324
325
    return $rs->next()->get_column('min');
326
}
327
328
=head2 get_unique_holidays
329
330
  my @unique_holidays = $calendar->get_unique_holidays();
331
332
Returns an array of all the date objects that are unique holidays.
333
334
=cut
335
336
sub get_unique_holidays {
337
    my $self = shift;
338
    my $exclude_past = shift || 1;
339
    my $branchcode = $self->{branchcode};
340
    my @unique_holidays;
341
    my $schema = Koha::Database->new->schema;
342
343
    my $rs = $schema->resultset('DiscreteCalendar')->search(
344
        {
345
            branchcode   => $branchcode,
346
            holiday_type => $HOLIDAYS->{EXCEPTION}
347
        },
348
        {
349
            select => [{ DATE => 'date' }, 'note' ],
350
            as     => [qw/ date note/],
351
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
352
        }
353
    );
354
    while (my $date = $rs->next()) {
355
        my $outputdate = dt_from_string($date->date(), 'iso');
356
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
357
        push @unique_holidays, {
358
            date       => $date->date(),
359
            outputdate => $outputdate,
360
            note       => $date->note()
361
        }
362
    }
363
364
    return @unique_holidays;
365
}
366
367
=head2 get_float_holidays
368
369
  my @float_holidays = $calendar->get_float_holidays();
370
371
Returns an array of all the date objects that are float holidays.
372
373
=cut
374
375
sub get_float_holidays {
376
    my $self = shift;
377
    my $exclude_past = shift || 1;
378
    my $branchcode = $self->{branchcode};
379
    my @float_holidays;
380
    my $schema = Koha::Database->new->schema;
381
382
    my $rs = $schema->resultset('DiscreteCalendar')->search(
383
        {
384
            branchcode   => $branchcode,
385
            holiday_type => $HOLIDAYS->{FLOAT}
386
        },
387
        {
388
            select => [{ DATE => 'date' }, 'note' ],
389
            as     => [qw/ date note/],
390
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
391
        }
392
    );
393
    while (my $date = $rs->next()) {
394
        my $outputdate = dt_from_string($date->date(), 'iso');
395
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
396
        push @float_holidays, {
397
            date        => $date->date(),
398
            outputdate  => $outputdate,
399
            note        => $date->note()
400
        }
401
    }
402
403
    return @float_holidays;
404
}
405
406
=head2 get_need_validation_holidays
407
408
  my @need_validation_holidays = $calendar->get_need_validation_holidays();
409
410
Returns an array of all the date objects that are float holidays in need of validation.
411
412
=cut
413
414
sub get_need_validation_holidays {
415
    my $self = shift;
416
    my $exclude_past = shift || 1;
417
    my $branchcode = $self->{branchcode};
418
    my @need_validation_holidays;
419
    my $schema = Koha::Database->new->schema;
420
421
    my $rs = $schema->resultset('DiscreteCalendar')->search(
422
        {
423
            branchcode   => $branchcode,
424
            holiday_type => $HOLIDAYS->{NEED_VALIDATION}
425
        },
426
        {
427
            select => [{ DATE => 'date' }, 'note' ],
428
            as     => [qw/ date note/],
429
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
430
        }
431
    );
432
    while (my $date = $rs->next()) {
433
        my $outputdate = dt_from_string($date->date(), 'iso');
434
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
435
        push @need_validation_holidays, {
436
            date        => $date->date(),
437
            outputdate  => $outputdate,
438
            note        => $date->note()
439
        }
440
    }
441
442
    return @need_validation_holidays;
443
}
444
445
=head2 get_repeatable_holidays
446
447
  my @repeatable_holidays = $calendar->get_repeatable_holidays();
448
449
Returns an array of all the date objects that are repeatable holidays.
450
451
=cut
452
453
sub get_repeatable_holidays {
454
    my $self = shift;
455
    my $exclude_past = shift || 1;
456
    my $branchcode = $self->{branchcode};
457
    my @repeatable_holidays;
458
    my $schema = Koha::Database->new->schema;
459
460
    my $rs = $schema->resultset('DiscreteCalendar')->search(
461
        {
462
            branchcode   => $branchcode,
463
            holiday_type => $HOLIDAYS->{'REPEATABLE'},
464
465
        },
466
        {
467
            select => \[ 'distinct DAY(date), MONTH(date), note'],
468
            as     => [qw/ day month note/],
469
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
470
        }
471
    );
472
473
    while (my $date = $rs->next()) {
474
        push @repeatable_holidays, {
475
            day => $date->get_column('day'),
476
            month => $date->get_column('month'),
477
            note => $date->note()
478
        };
479
    }
480
481
    return @repeatable_holidays;
482
}
483
484
=head2 get_week_days_holidays
485
486
  my @week_days_holidays = $calendar->get_week_days_holidays;
487
488
Returns an array of all the date objects that are weekly holidays.
489
490
=cut
491
492
sub get_week_days_holidays {
493
    my $self = shift;
494
    my $exclude_past = shift || 1;
495
    my $branchcode = $self->{branchcode};
496
    my @week_days;
497
    my $schema = Koha::Database->new->schema;
498
499
    my $rs = $schema->resultset('DiscreteCalendar')->search(
500
        {
501
            holiday_type => $HOLIDAYS->{WEEKLY},
502
            branchcode   => $branchcode,
503
        },
504
        {
505
            select   => \[ 'distinct DAYOFWEEK(date), note'],
506
            as       => [qw/ weekday note /],
507
            where    => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
508
        }
509
    );
510
511
    while (my $date = $rs->next()) {
512
        push @week_days, {
513
            weekday => ($date->get_column('weekday') -1),
514
            note    => $date->note()
515
        };
516
    }
517
518
    return @week_days;
519
}
520
521
=head2 edit_holiday
522
523
Modifies a date or a range of dates
524
525
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
526
527
C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range.
528
529
C<$holiday_type> Is the type of the holiday :
530
    E : Exception holiday, single day.
531
    F : Floating holiday, different day each year.
532
    N : Needs validation, copied float holiday from the past
533
    R : Repeatable holiday, repeated on same date.
534
    W : Weekly holiday, same day of the week.
535
536
C<$open_hour> Is the opening hour.
537
C<$close_hour> Is the closing hour.
538
C<$start_date> Is the start of the range of dates.
539
C<$end_date> Is the end of the range of dates.
540
C<$delete_type> Delete all
541
C<$today> Today based on the local date, using JavaScript.
542
543
=cut
544
545
sub edit_holiday {
546
    my $self = shift;
547
    my ($params) = @_;
548
549
    my $title        = $params->{title};
550
    my $weekday      = $params->{weekday} || '';
551
    my $holiday_type = $params->{holiday_type};
552
553
    my $start_date   = $params->{start_date};
554
    my $end_date     = $params->{end_date};
555
556
    my $open_hour    = $params->{open_hour} || '';
557
    my $close_hour   = $params->{close_hour} || '';
558
559
    my $delete_type  = $params->{delete_type} || undef;
560
    my $today        = $params->{today} || dt_from_string()->truncate( to => 'day' );
561
562
    my $branchcode = $self->{branchcode};
563
564
    # When override param is set, this function will allow past dates to be set as holidays,
565
    # otherwise it will not. This is meant to only be used for testing.
566
    my $override = $params->{override} || 0;
567
568
    my $schema = Koha::Database->new->schema;
569
    $schema->{AutoCommit} = 0;
570
    $schema->storage->txn_begin;
571
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
572
573
    #String dates for Database usage
574
    my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
575
    my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
576
    $today = $dtf->format_datetime($today->clone->truncate(to => 'day'));
577
    my %updateValues = (
578
        is_opened    => 0,
579
        note         => $title,
580
        holiday_type => $holiday_type,
581
    );
582
    $updateValues{open_hour} = $open_hour if $open_hour ne '';
583
    $updateValues{close_hour} = $close_hour if $close_hour ne '';
584
585
    if ($holiday_type eq $HOLIDAYS->{WEEKLY}) {
586
        #Update weekly holidays
587
        if ($start_date_string eq $end_date_string) {
588
            $end_date_string = $self->get_max_date();
589
        }
590
        my $rs = $schema->resultset('DiscreteCalendar')->search(
591
            {
592
                branchcode => $branchcode,
593
            },
594
            {
595
                where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string],
596
            }
597
        );
598
599
        while (my $date = $rs->next()) {
600
            $date->update(\%updateValues);
601
        }
602
    } elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) {
603
        #Update Exception Float and Needs Validation holidays
604
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
605
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
606
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
607
        }
608
        $where->{date}{'>='} = $today unless $override;
609
610
        my $rs = $schema->resultset('DiscreteCalendar')->search(
611
            {
612
                branchcode => $branchcode,
613
            },
614
            {
615
                where => $where,
616
            }
617
        );
618
        while (my $date = $rs->next()) {
619
            $date->update(\%updateValues);
620
        }
621
622
    } elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) {
623
        #Update repeatable holidays
624
        my $parser = DateTime::Format::Strptime->new(
625
           pattern  => '%m-%d',
626
           on_error => 'croak',
627
        );
628
        #Format the dates to have only month-day ex: 01-04 for January 4th
629
        $start_date = $parser->format_datetime($start_date);
630
        $end_date = $parser->format_datetime($end_date);
631
        my $where = { -and => [ \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] };
632
        push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override;
633
        my $rs = $schema->resultset('DiscreteCalendar')->search(
634
            {
635
                branchcode => $branchcode,
636
            },
637
            {
638
                where => $where,
639
            }
640
        );
641
        while (my $date = $rs->next()) {
642
            $date->update(\%updateValues);
643
        }
644
645
    } else {
646
        #Update date(s)/Remove holidays
647
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
648
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
649
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
650
        }
651
        $where->{date}{'>='} = $today unless $override;
652
653
        my $rs = $schema->resultset('DiscreteCalendar')->search(
654
            {
655
                branchcode => $branchcode,
656
            },
657
            {
658
                where => $where,
659
            }
660
        );
661
        #If none, the date(s) will be normal days, else,
662
        if ($holiday_type eq 'none') {
663
            $updateValues{holiday_type} ='';
664
            $updateValues{is_opened} =1;
665
        } else {
666
            delete $updateValues{holiday_type};
667
            delete $updateValues{is_opened};
668
        }
669
670
        while (my $date = $rs->next()) {
671
            if ($delete_type) {
672
                if ($date->holiday_type() eq $HOLIDAYS->{WEEKLY}) {
673
                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
674
                } elsif ($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}) {
675
                    $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today);
676
                }
677
            } else {
678
                $date->update(\%updateValues);
679
            }
680
        }
681
    }
682
    $schema->storage->txn_commit;
683
    $schema->{AutoCommit} = 1;
684
}
685
686
=head2 remove_weekly_holidays
687
688
    $calendar->remove_weekly_holidays($weekday, $updateValues, $today);
689
690
Removes a weekly holiday and updates the days' parameters
691
C<$weekday> is the weekday to un-holiday
692
C<$updatevalues> is hashref containing the new parameters
693
C<$today> is today's date
694
695
=cut
696
697
sub remove_weekly_holidays {
698
    my ($self, $weekday, $updateValues, $today) = @_;
699
    my $branchcode = $self->{branchcode};
700
    my $schema = Koha::Database->new->schema;
701
702
    my $rs = $schema->resultset('DiscreteCalendar')->search(
703
        {
704
            branchcode   => $branchcode,
705
            is_opened    => 0,
706
            holiday_type => $HOLIDAYS->{WEEKLY}
707
        },
708
        {
709
            where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]},
710
        }
711
    );
712
713
    while (my $date = $rs->next()) {
714
        $date->update($updateValues);
715
    }
716
}
717
718
=head2 remove_repeatable_holidays
719
720
    $calendar->remove_repeatable_holidays($startDate, $endDate, $today);
721
722
Removes a repeatable holiday and updates the days' parameters
723
C<$startDatey> is the start date of the repeatable holiday
724
C<$endDate> is the end date of the repeatble holiday
725
C<$updatevalues> is hashref containing the new parameters
726
C<$today> is today's date
727
728
=cut
729
730
sub remove_repeatable_holidays {
731
    my ($self, $startDate, $endDate, $updateValues, $today) = @_;
732
    my $branchcode = $self->{branchcode};
733
    my $schema = Koha::Database->new->schema;
734
    my $parser = DateTime::Format::Strptime->new(
735
        pattern  => '%m-%d',
736
        on_error => 'croak',
737
    );
738
    #Format the dates to have only month-day ex: 01-04 for January 4th
739
    $startDate = $parser->format_datetime($startDate);
740
    $endDate = $parser->format_datetime($endDate);
741
742
    my $rs = $schema->resultset('DiscreteCalendar')->search(
743
        {
744
            branchcode   => $branchcode,
745
            is_opened    => 0,
746
            holiday_type => $HOLIDAYS->{REPEATABLE},
747
        },
748
        {
749
            where => { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]},
750
        }
751
    );
752
753
    while (my $date = $rs->next()) {
754
        $date->update($updateValues);
755
    }
756
}
757
758
=head2 copy_to_branch
759
760
  $calendar->copy_to_branch($branch2);
761
762
Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self>
763
but not in C<$branch2>
764
765
C<$branch2> the branch to copy into
766
767
=cut
768
769
sub copy_to_branch {
770
    my ($self, $newBranch) =@_;
771
    my $branchcode = $self->{branchcode};
772
    my $schema = Koha::Database->new->schema;
773
774
    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
775
        {
776
            branchcode => $branchcode
777
        },
778
        {
779
            columns => [ qw/ date is_opened note holiday_type open_hour close_hour /]
780
        }
781
    );
782
    while (my $copyDate = $copyFrom->next()) {
783
        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
784
            {
785
                branchcode => $newBranch,
786
                date       => $copyDate->date(),
787
            },
788
            {
789
                columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /]
790
            }
791
        );
792
        #if the date does not exist in the copyTO branch, than skip it.
793
        if ($copyTo->count ==0) {
794
            next;
795
        }
796
        $copyTo->next()->update({
797
            is_opened    => $copyDate->is_opened(),
798
            holiday_type => $copyDate->holiday_type(),
799
            note         => $copyDate->note(),
800
            open_hour    => $copyDate->open_hour(),
801
            close_hour   => $copyDate->close_hour()
802
        });
803
    }
804
}
805
806
=head2 is_opened
807
808
    $calendar->is_opened($date)
809
810
Returns whether the library is open on C<$date>
811
812
=cut
813
814
sub is_opened {
815
    my($self, $date) = @_;
816
    my $branchcode = $self->{branchcode};
817
    my $schema = Koha::Database->new->schema;
818
    my $dtf = $schema->storage->datetime_parser;
819
    $date= $dtf->format_datetime($date);
820
    #if the date is not found
821
    my $is_opened = -1;
822
    my $rs = $schema->resultset('DiscreteCalendar')->search(
823
        {
824
            branchcode => $branchcode,
825
        },
826
        {
827
            where => \['date = DATE(?)', $date]
828
        }
829
    );
830
    $is_opened = $rs->next()->is_opened() if $rs->count() != 0;
831
832
    return $is_opened;
833
}
834
835
=head2 is_holiday
836
837
    $calendar->is_holiday($date)
838
839
Returns whether C<$date> is a holiday or not
840
841
=cut
842
843
sub is_holiday {
844
    my($self, $date) = @_;
845
    my $branchcode = $self->{branchcode};
846
    my $schema = Koha::Database->new->schema;
847
    my $dtf = $schema->storage->datetime_parser;
848
    $date= $dtf->format_datetime($date);
849
    #if the date is not found
850
    my $isHoliday = -1;
851
    my $rs = $schema->resultset('DiscreteCalendar')->search(
852
        {
853
            branchcode => $branchcode,
854
        },
855
        {
856
            where => \['date = DATE(?)', $date]
857
        }
858
    );
859
860
    if ($rs->count() != 0) {
861
        $isHoliday = ($rs->first()->is_opened() ? 0 : 1);
862
    }
863
864
    return $isHoliday;
865
}
866
867
=head2 copy_holiday
868
869
  $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
870
871
Copies a holiday's parameters from a range to a new range
872
C<$from_startDate> the source holiday's start date
873
C<$from_endDate> the source holiday's end date
874
C<$to_startDate> the destination holiday's start date
875
C<$to_endDate> the destination holiday's end date
876
C<$daysnumber> the number of days in the range.
877
878
Both ranges should have the same number of days in them.
879
880
=cut
881
882
sub copy_holiday {
883
    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
884
    my $branchcode = $self->{branchcode};
885
    my $copyFromType = $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
886
    my $schema = Koha::Database->new->schema;
887
    my $dtf = $schema->storage->datetime_parser;
888
889
    if ($copyFromType eq 'oneDay') {
890
        my $where;
891
        $to_startDate = $dtf->format_datetime($to_startDate);
892
        if ($to_startDate && $to_endDate) {
893
            $to_endDate = $dtf->format_datetime($to_endDate);
894
            $where = { date => { -between => [$to_startDate, $to_endDate]}};
895
        } else {
896
            $where = { date => $to_startDate };
897
        }
898
899
        $from_startDate = $dtf->format_datetime($from_startDate);
900
        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
901
            {
902
                branchcode => $branchcode,
903
                date       => $from_startDate
904
            }
905
        );
906
        my $toDates = $schema->resultset('DiscreteCalendar')->search(
907
            {
908
                branchcode => $branchcode,
909
            },
910
            {
911
                where => $where
912
            }
913
        );
914
915
        my $copyDate = $fromDate->next();
916
        while (my $date = $toDates->next()) {
917
            $date->update({
918
                is_opened    => $copyDate->is_opened(),
919
                holiday_type => $copyDate->holiday_type(),
920
                note         => $copyDate->note(),
921
                open_hour    => $copyDate->open_hour(),
922
                close_hour   => $copyDate->close_hour()
923
            })
924
        }
925
926
    } else {
927
        my $endDate = dt_from_string($from_endDate);
928
        $to_startDate = $dtf->format_datetime($to_startDate);
929
        $to_endDate = $dtf->format_datetime($to_endDate);
930
        if ($daysnumber == 7) {
931
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
932
                my $formatedDate = $dtf->format_datetime($tempDate);
933
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
934
                    {
935
                        branchcode => $branchcode,
936
                        date       => $formatedDate,
937
                    },
938
                    {
939
                        select  => [{ DAYOFWEEK => 'date' }],
940
                        as      => [qw/ weekday /],
941
                        columns =>[ qw/ holiday_type note open_hour close_hour note/]
942
                    }
943
                );
944
                my $copyDate = $fromDate->next();
945
                my $weekday = $copyDate->get_column('weekday');
946
947
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
948
                    {
949
                        branchcode => $branchcode,
950
951
                    },
952
                    {
953
                        where => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday},
954
                    }
955
                );
956
                my $copyToDate = $toDate->next();
957
                $copyToDate->update({
958
                    is_opened    => $copyDate->is_opened(),
959
                    holiday_type => $copyDate->holiday_type(),
960
                    note         => $copyDate->note(),
961
                    open_hour    => $copyDate->open_hour(),
962
                    close_hour   => $copyDate->close_hour()
963
                });
964
965
            }
966
        } else {
967
            my $to_startDate = dt_from_string($to_startDate);
968
            my $to_endDate = dt_from_string($to_endDate);
969
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
970
                my $from_formatedDate = $dtf->format_datetime($tempDate);
971
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
972
                    {
973
                        branchcode => $branchcode,
974
                        date       => $from_formatedDate,
975
                    },
976
                    {
977
                        order_by => { -asc => 'date' }
978
                    }
979
                );
980
                my $to_formatedDate = $dtf->format_datetime($to_startDate);
981
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
982
                    {
983
                        branchcode => $branchcode,
984
                        date       => $to_formatedDate
985
                    },
986
                    {
987
                        order_by => { -asc => 'date' }
988
                    }
989
                );
990
                my $copyDate = $fromDate->next();
991
                $toDate->next()->update({
992
                    is_opened    => $copyDate->is_opened(),
993
                    holiday_type => $copyDate->holiday_type(),
994
                    note         => $copyDate->note(),
995
                    open_hour    => $copyDate->open_hour(),
996
                    close_hour   => $copyDate->close_hour()
997
                });
998
                $to_startDate->add(days =>1);
999
            }
1000
        }
1001
1002
1003
    }
1004
}
1005
1006
=head2 days_between
1007
1008
   $cal->days_between( $start_date, $end_date )
1009
1010
Calculates the number of days the library is opened between C<$start_date> and C<$end_date>
1011
1012
=cut
1013
1014
sub days_between {
1015
    my ($self, $start_date, $end_date, ) = @_;
1016
    my $branchcode = $self->{branchcode};
1017
1018
    if ( $start_date->compare($end_date) > 0 ) {
1019
        # swap dates
1020
        ($start_date, $end_date) = ($end_date, $start_date);
1021
    }
1022
1023
    my $schema = Koha::Database->new->schema;
1024
    my $dtf = $schema->storage->datetime_parser;
1025
    $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
1026
    $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
1027
1028
    my $days_between = $schema->resultset('DiscreteCalendar')->search(
1029
        {
1030
            branchcode => $branchcode,
1031
            is_opened  => 1,
1032
        },
1033
        {
1034
            where => \['date >= date(?) AND date < date(?)', $start_date, $end_date]
1035
        }
1036
    );
1037
1038
    return DateTime::Duration->new( days => $days_between->count());
1039
}
1040
1041
=head2 next_open_day
1042
1043
   $open_date = $self->next_open_day($base_date);
1044
1045
Returns a string representing the next day the library is open, starting from C<$base_date>
1046
1047
=cut
1048
1049
sub next_open_day {
1050
    my ( $self, $date ) = @_;
1051
    my $branchcode = $self->{branchcode};
1052
    my $schema = Koha::Database->new->schema;
1053
    my $dtf = $schema->storage->datetime_parser;
1054
    my $formatted_date = $dtf->format_datetime($date);
1055
1056
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1057
        {
1058
            branchcode => $branchcode,
1059
            is_opened  => 1,
1060
        },
1061
        {
1062
            where    => \['date > date(?)', $formatted_date],
1063
            order_by => { -asc => 'date' },
1064
            rows     => 1
1065
        }
1066
    );
1067
1068
    my $next = $rs->next();
1069
    unless ( $next ) {
1070
        warn "No next opened date in calendar. Reduced to current date: $formatted_date.";
1071
        return $date;
1072
    }
1073
    return dt_from_string( $next->date(), 'iso');
1074
}
1075
1076
=head2 prev_open_day
1077
1078
   $open_date = $self->prev_open_day($base_date);
1079
1080
Returns a string representing the closest previous day the library was open, starting from C<$base_date>
1081
1082
=cut
1083
1084
sub prev_open_day {
1085
    my ( $self, $date ) = @_;
1086
    my $branchcode = $self->{branchcode};
1087
    my $schema = Koha::Database->new->schema;
1088
    my $dtf = $schema->storage->datetime_parser;
1089
    my $formatted_date = $dtf->format_datetime($date);
1090
1091
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1092
        {
1093
            branchcode => $branchcode,
1094
            is_opened  => 1,
1095
        },
1096
        {
1097
            where    => \['date < date(?)', $formatted_date],
1098
            order_by => { -desc => 'date' },
1099
            rows     => 1
1100
        }
1101
    );
1102
1103
    my $prev = $rs->next();
1104
    unless ( $prev ) {
1105
        warn "No previous opened date in calendar. Reduced to current date: $formatted_date.";
1106
        return $date;
1107
    }
1108
    return dt_from_string( $prev->date(), 'iso');
1109
}
1110
1111
=head2 days_forward
1112
1113
    $fwrd_date = $calendar->days_forward($start, $count)
1114
1115
Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed.
1116
1117
=cut
1118
1119
sub days_forward {
1120
    my $self = shift;
1121
    my $start_dt = shift;
1122
    my $num_days = shift;
1123
1124
    return $start_dt unless $num_days > 0;
1125
1126
    my $base_dt = $start_dt->clone();
1127
1128
    while ($num_days--) {
1129
        $base_dt = $self->next_open_day($base_dt);
1130
    }
1131
1132
    return $base_dt;
1133
}
1134
1135
=head2 hours_between
1136
1137
    $hours = $calendar->hours_between($start_dt, $end_dt)
1138
1139
Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise
1140
version, which simply calculates the number of day times 24. To take opening hours into account
1141
see C<open_hours_between>/
1142
1143
=cut
1144
1145
sub hours_between {
1146
    my ($self, $start_dt, $end_dt) = @_;
1147
    my $branchcode = $self->{branchcode};
1148
    my $schema = Koha::Database->new->schema;
1149
    my $dtf = $schema->storage->datetime_parser;
1150
    my $start_date = $start_dt->clone();
1151
    my $end_date = $end_dt->clone();
1152
    my $duration = $end_date->delta_ms($start_date);
1153
    $start_date->truncate( to => 'day' );
1154
    $end_date->truncate( to => 'day' );
1155
1156
    # NB this is a kludge in that it assumes all days are 24 hours
1157
    # However for hourly loans the logic should be expanded to
1158
    # take into account open/close times then it would be a duration
1159
    # of library open hours
1160
    my $skipped_days = 0;
1161
    $start_date = $dtf->format_datetime($start_date);
1162
    $end_date = $dtf->format_datetime($end_date);
1163
    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
1164
        {
1165
            branchcode => $branchcode,
1166
            is_opened  => 0
1167
        },
1168
        {
1169
            where => {date => {-between => [$start_date, $end_date]}},
1170
        }
1171
    );
1172
1173
    if ($skipped_days = $hours_between->count()) {
1174
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
1175
    }
1176
1177
    return $duration;
1178
}
1179
1180
=head2 open_hours_between
1181
1182
  $hours = $calendar->open_hours_between($start_date, $end_date)
1183
1184
Returns the number of hours between C<$start_date> and C<$end_date>, taking into
1185
account the opening hours of the library.
1186
1187
=cut
1188
1189
sub open_hours_between {
1190
    my ($self, $start_date, $end_date) = @_;
1191
    my $branchcode = $self->{branchcode};
1192
    my $schema = Koha::Database->new->schema;
1193
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1194
    $start_date = $dtf->format_datetime($start_date);
1195
    $end_date = $dtf->format_datetime($end_date);
1196
1197
    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
1198
        {
1199
            branchcode => $branchcode,
1200
            is_opened  => 1,
1201
        },
1202
        {
1203
            select => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'],
1204
            as     => [qw /hours_between/],
1205
            where  => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1206
        }
1207
    );
1208
1209
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1210
        {
1211
            branchcode => $branchcode,
1212
        },
1213
        {
1214
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1215
            rows => 1,
1216
        }
1217
    );
1218
1219
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1220
        {
1221
            branchcode => $branchcode,
1222
        },
1223
        {
1224
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1225
            rows => 1,
1226
        }
1227
    );
1228
1229
    #Capture the time portion of the date
1230
    $start_date =~ /\s(.*)/;
1231
    my $loan_date_time = $1;
1232
    $end_date =~ /\s(.*)/;
1233
    my $return_date_time = $1;
1234
1235
    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
1236
        {
1237
            branchcode => $branchcode,
1238
            is_opened  => 1,
1239
        },
1240
        {
1241
            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()],
1242
            as     => [qw /not_used_hours/],
1243
        }
1244
    );
1245
1246
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1247
}
1248
1249
=head2 addDuration
1250
1251
  my $dt = $calendar->addDuration($date, $dur, $unit)
1252
1253
C<$date> is a DateTime object representing the starting date of the interval.
1254
C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy)
1255
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
1256
1257
=cut
1258
1259
sub addDuration {
1260
    my ( $self, $startdate, $add_duration, $unit ) = @_;
1261
1262
    # Default to days duration (legacy support I guess)
1263
    if ( ref $add_duration ne 'DateTime::Duration' ) {
1264
        $add_duration = DateTime::Duration->new( days => $add_duration );
1265
    }
1266
1267
    $unit ||= 'days'; # default days ?
1268
    my $dt;
1269
1270
    if ( $unit eq 'hours' ) {
1271
        # Fixed for legacy support. Should be set as a branch parameter
1272
        my $return_by_hour = 10;
1273
1274
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1275
    } else {
1276
        # days
1277
        $dt = $self->addDays($startdate, $add_duration);
1278
    }
1279
1280
    return $dt;
1281
}
1282
1283
=head2 addHours
1284
1285
  $end = $calendar->addHours($start, $hours_duration, $return_by_hour)
1286
1287
Add C<$hours_duration> to C<$start> date.
1288
C<$return_by_hour> is an integer value representing the opening hour for the branch
1289
1290
=cut
1291
1292
sub addHours {
1293
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
1294
    my $base_date = $startdate->clone();
1295
1296
    $base_date->add_duration($hours_duration);
1297
1298
    # If we are using the calendar behave for now as if Datedue
1299
    # was the chosen option (current intended behaviour)
1300
1301
    if ( $self->{days_mode} ne 'Days' &&
1302
    $self->is_holiday($base_date) ) {
1303
1304
        if ( $hours_duration->is_negative() ) {
1305
            $base_date = $self->prev_open_day($base_date);
1306
        } else {
1307
            $base_date = $self->next_open_day($base_date);
1308
        }
1309
1310
        $base_date->set_hour($return_by_hour);
1311
1312
    }
1313
1314
    return $base_date;
1315
}
1316
1317
=head2 addDays
1318
1319
  $date = $calendar->addDays($start, $duration)
1320
1321
Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set
1322
to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue'
1323
it calculates the date normally, and then pushes to result to the next open day.
1324
1325
=cut
1326
1327
sub addDays {
1328
    my ( $self, $startdate, $days_duration ) = @_;
1329
    my $base_date = $startdate->clone();
1330
1331
    $self->{days_mode} ||= q{};
1332
1333
    if ( $self->{days_mode} eq 'Calendar' ) {
1334
        # use the calendar to skip all days the library is closed
1335
        # when adding
1336
        my $days = abs $days_duration->in_units('days');
1337
1338
        if ( $days_duration->is_negative() ) {
1339
            while ($days) {
1340
                $base_date = $self->prev_open_day($base_date);
1341
                --$days;
1342
            }
1343
        } else {
1344
            while ($days) {
1345
                $base_date = $self->next_open_day($base_date);
1346
                --$days;
1347
            }
1348
        }
1349
1350
    } else { # Days or Datedue
1351
        # use straight days, then use calendar to push
1352
        # the date to the next open day if Datedue
1353
        $base_date->add_duration($days_duration);
1354
1355
        if ( $self->{days_mode} eq 'Datedue' ) {
1356
            # Datedue, then use the calendar to push
1357
            # the date to the next open day if holiday
1358
            if (!$self->is_opened($base_date) ) {
1359
1360
                if ( $days_duration->is_negative() ) {
1361
                    $base_date = $self->prev_open_day($base_date);
1362
                } else {
1363
                    $base_date = $self->next_open_day($base_date);
1364
                }
1365
            }
1366
        }
1367
    }
1368
1369
    return $base_date;
1370
}
1371
1372
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 242-248 sub set_waiting { Link Here
242
                branchcode   => $self->branchcode,
242
                branchcode   => $self->branchcode,
243
            }
243
            }
244
        );
244
        );
245
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
245
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode, days_mode => $daysmode });
246
246
247
        $new_expiration_date = $calendar->days_forward( dt_from_string(), $max_pickup_delay );
247
        $new_expiration_date = $calendar->days_forward( dt_from_string(), $max_pickup_delay );
248
    }
248
    }
(-)a/admin/branches.pl (+3 lines)
Lines 152-157 if ( $op eq 'add_form' ) { Link Here
152
                        }
152
                        }
153
                    }
153
                    }
154
154
155
                    Koha::DiscreteCalendar->add_new_branch(undef, $branchcode);
156
155
                    push @messages, { type => 'message', code => 'success_on_insert' };
157
                    push @messages, { type => 'message', code => 'success_on_insert' };
156
                }
158
                }
157
            );
159
            );
Lines 197-202 if ( $op eq 'add_form' ) { Link Here
197
    if ( $@ or not $deleted ) {
199
    if ( $@ or not $deleted ) {
198
        push @messages, { type => 'alert', code => 'error_on_delete' };
200
        push @messages, { type => 'alert', code => 'error_on_delete' };
199
    } else {
201
    } else {
202
        Koha::DiscreteCalendar->delete_branch($branchcode);
200
        push @messages, { type => 'message', code => 'success_on_delete' };
203
        push @messages, { type => 'message', code => 'success_on_delete' };
201
    }
204
    }
202
    $op = 'list';
205
    $op = 'list';
(-)a/circ/returns.pl (-1 / +2 lines)
Lines 45-54 use C4::Reserves qw( ModReserve ModReserveAffect GetOtherReserves ); 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 216-221 my $dotransfer = $query->param('dotransfer'); Link Here
216
my $canceltransfer = $query->param('canceltransfer');
216
my $canceltransfer = $query->param('canceltransfer');
217
my $transit = $query->param('transit');
217
my $transit = $query->param('transit');
218
my $dest = $query->param('dest');
218
my $dest = $query->param('dest');
219
my $calendar    = Koha::DiscreteCalendar->new({ branchcode => $userenv_branch });
219
#dropbox: get last open day (today - 1)
220
#dropbox: get last open day (today - 1)
220
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
221
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
221
222
(-)a/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css (+213 lines)
Line 0 Link Here
1
#jcalendar-container .ui-datepicker {
2
    float: left;
3
    font-size: 185%;
4
}
5
6
#holidayweeklyrepeatable,
7
#holidaysyearlyrepeatable,
8
#holidaysunique,
9
#holidayexceptions {
10
    font-size: 90%;
11
    margin-bottom: 1em;
12
}
13
14
#showHoliday {
15
    margin: .5em 0;
16
}
17
18
.key {
19
    padding: 3px;
20
    white-space: nowrap;
21
    line-height: 230%;
22
}
23
24
.ui-datepicker {
25
    font-size: 150%;
26
}
27
28
.ui-datepicker th,
29
.ui-datepicker .ui-datepicker-title select {
30
    font-size: 80%;
31
}
32
33
.ui-datepicker td a {
34
    padding: .5em;
35
}
36
37
.ui-datepicker td span {
38
    padding: .5em;
39
    border: 1px solid #BCBCBC;
40
}
41
42
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
43
    font-size: 80%;
44
}
45
46
.key {
47
    padding: 3px;
48
    white-space: nowrap;
49
    line-height: 230%;
50
}
51
52
.normalday {
53
    background-color: #EDEDED;
54
    color: #000;
55
    border: 1px solid #BCBCBC;
56
}
57
58
.ui-datepicker-unselectable {
59
    padding: .5em;
60
    white-space: nowrap;
61
}
62
63
.ui-state-disabled {
64
    padding: .5em;
65
    white-space: nowrap;
66
}
67
68
.exception {
69
    background-color: #b3d4ff;
70
    color: #000;
71
    border: 1px solid #BCBCBC;
72
}
73
74
.past-date {
75
    background-color: #e6e6e6;
76
    color: #555;
77
    border: 1px solid #BCBCBC;
78
}
79
80
td.past-date a.ui-state-default {
81
    background: #e6e6e6;
82
    color: #555;
83
}
84
85
.float {
86
    background-color: #6f3;
87
    color: #000;
88
    border: 1px solid #BCBCBC;
89
}
90
91
.holiday {
92
    background-color: #ffaeae;
93
    color: #000;
94
    border: 1px solid #BCBCBC;
95
}
96
97
.repeatableweekly {
98
    background-color: #FF9;
99
    color: #000;
100
    border: 1px solid #BCBCBC;
101
}
102
103
.repeatableyearly {
104
    background-color: #FC6;
105
    color: #000;
106
    border: 1px solid #BCBCBC;
107
}
108
109
td.exception a.ui-state-default {
110
    background: #b3d4ff none;
111
    color: #000;
112
    border: 1px solid #BCBCBC;
113
}
114
115
td.float a.ui-state-default {
116
    background: #6f3 none;
117
    color: #000;
118
    border: 1px solid #BCBCBC;
119
}
120
121
td.holiday a.ui-state-default {
122
    background: #ffaeae none;
123
    color: #000;
124
    border: 1px solid #BCBCBC;
125
}
126
127
td.repeatableweekly a.ui-state-default {
128
    background: #FF9 none;
129
    color: #000;
130
    border: 1px solid #BCBCBC;
131
}
132
133
td.repeatableyearly a.ui-state-default {
134
    background: #FC6 none;
135
    color: #000;
136
    border: 1px solid #BCBCBC;
137
}
138
139
.information {
140
    background-color: #DCD2F1;
141
    width: 300px;
142
    display: none;
143
    border: 1px solid #000;
144
    color: #000;
145
    font-size: 8pt;
146
    font-weight: 700;
147
    background-color: #FFD700;
148
    cursor: pointer;
149
    padding: 2px;
150
}
151
152
.panel {
153
    z-index: 1;
154
    display: none;
155
    float: left;
156
    border: 3px solid #CCC;
157
    padding: 3px;
158
    margin-top: .3em;
159
    margin-left: 15px;
160
    background-color: #FEFEFE;
161
}
162
163
fieldset.brief {
164
    border: 0;
165
    margin: 0;
166
}
167
168
h1 select {
169
    width: 20em;
170
}
171
172
fieldset.brief ol {
173
    font-size: 100%;
174
}
175
176
fieldset.brief li,
177
fieldset.brief li.radio {
178
    padding: .2em 0;
179
}
180
181
.CopyDatePanel {
182
    margin-left: 15px;
183
}
184
185
.help {
186
    margin: .3em 0;
187
    border: 1px solid #EEE;
188
    padding: .3em .7em;
189
    font-size: 90%;
190
}
191
192
.calendar td,
193
.calendar th,
194
.calendar .button,
195
.calendar tbody .day {
196
    padding: .7em;
197
    font-size: 110%;
198
}
199
200
.calendar {
201
    width: auto;
202
    border: 0;
203
}
204
205
.copyHoliday form li {
206
    display: table-row;
207
}
208
209
.copyHoliday form li b,
210
.copyHoliday form li input {
211
    display: table-cell;
212
    margin-bottom: 2px;
213
}
(-)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 (+708 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; Tools &rsaquo; Koha &rsaquo</title>
7
[% INCLUDE 'doc-head-close.inc' %]
8
[% Asset.css("css/discretecalendar.css") | $raw %]
9
</head>
10
11
<body id="tools_holidays" class="tools">
12
[% INCLUDE 'header.inc' %]
13
[% INCLUDE 'cat-search.inc' %]
14
15
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
16
    <ol>
17
        <li>
18
            <a href="/cgi-bin/koha/mainpage.pl">Home</a>
19
        </li>
20
        <li>
21
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
22
        </li>
23
        <li>
24
            <a href="#" aria-current="page">
25
                [% Branches.GetName( branch ) | html %] calendar
26
            </a>
27
        </li>
28
    </ol>
29
</nav>
30
31
<div id="main" class="main container-fluid">
32
    <div class="row">
33
        <div class="col-sm-10 col-sm-push-2">
34
            <main>
35
                [% IF no_branch_selected %]
36
                <div class="dialog alert">
37
                    <strong>No library set!</strong>
38
                </div>
39
                [% END %]
40
41
                [% UNLESS datesInfos %]
42
                <div class="dialog alert">
43
                    <strong>Error!</strong> You have to run add_days_discrete_calendar.pl in order to use Discrete Calendar.
44
                </div>
45
                [% END %]
46
47
                [% IF date_format_error %]
48
                <div class="dialog alert">
49
                    <strong>Error!</strong> Date format error. Please try again.
50
                </div>
51
                [% END %]
52
53
                [% IF cannot_edit_past_dates %]
54
                <div class="alert alert-danger">
55
                    <strong>Error!</strong> You cannot edit the past. The date '[% error_date | html %]' was out of range for the requested action.
56
                </div>
57
                [% END %]
58
59
                <h2>[% Branches.GetName( branch ) | html %] calendar</h2>
60
61
                <div class="row">
62
                    <div class="col-sm-8">
63
                        <label for="branch">Define the holidays for:</label>
64
                        <form id="copyCalendar-form" method="post">
65
                            <select id="branch" name="branch">
66
                                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
67
                            </select>
68
                            Copy calendar to
69
                            <select id='newBranch' name ='newBranch'>
70
                                <option value=""></option>
71
                                [% FOREACH l IN Branches.all() %]
72
                                    [% UNLESS branch == l.branchcode %]
73
                                    <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
74
                                    [% END %]
75
                                [% END %]
76
                            </select>
77
                            <input type="hidden" name="action" value="copyBranch" />
78
                            <input type="submit" value="Clone">
79
                        </form>
80
81
                        <h3>Calendar information</h3>
82
83
                        <div id="jcalendar-container"></div>
84
85
                        <!-- ***************************** Panel to deal with new holidays ********************** -->
86
                        <div class="panel newHoliday" id="newHoliday">
87
                            <form id="newHoliday-form" method="post">
88
                                <fieldset class="brief">
89
                                    <h3>Edit date details</h3>
90
                                    <span id="holtype"></span>
91
                                    <ol>
92
                                        <li>
93
                                            <strong>Library:</strong>
94
                                            <span id="newBranchNameOutput"></span>
95
                                            <input type="hidden" id="branch" name="branch" />
96
                                        </li>
97
                                        <li>
98
                                            <strong>From date:</strong>
99
                                            <span id="newDaynameOutput"></span>,
100
101
                                            [% IF ( dateformat == "us" ) %]<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>
102
                                            [% ELSIF ( dateformat == "metric" ) %]<span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>
103
                                            [% ELSIF ( dateformat == "dmydot" ) %]<span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>
104
                                            [% ELSE %]<span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>[% END %]
105
106
                                            <input type="hidden" id="newDayname" name="showDayname" />
107
                                            <input type="hidden" id="Day" name="Day" />
108
                                            <input type="hidden" id="Month" name="Month" />
109
                                            <input type="hidden" id="Year" name="Year" />
110
                                        </li>
111
                                        <li class="dateinsert">
112
                                            <strong>To date:</strong>
113
                                            <input type="text" id="to_date_datepicker" size="20" class="datepicker" />
114
                                        </li>
115
                                        <li>
116
                                            <label for="title">Title: </label>
117
                                            <input type="text" name="Title" id="title" size="35" />
118
                                        </li>
119
                                        <li id="holidayType">
120
                                            <label for="holidayType">Date type</label>
121
                                            <select name ='holidayType'>
122
                                                <option value="empty"></option>
123
                                                <option value="none">Working day</option>
124
                                                <option value="E">Unique holiday</option>
125
                                                <option value="W">Weekly holiday</option>
126
                                                <option value="R">Repeatable holiday</option>
127
                                                <option value="F">Floating holiday</option>
128
                                                <option value="N" disabled>Need validation</option>
129
                                            </select>
130
                                        </li>
131
                                        <li id="days_of_week">
132
                                            <label for="day_of_week">Week day</label>
133
                                            <select name ='day_of_week'>
134
                                                <option value="everyday">Everyday</option>
135
                                                <option value="1">Sundays</option>
136
                                                <option value="2">Mondays</option>
137
                                                <option value="3">Tuesdays</option>
138
                                                <option value="4">Wednesdays</option>
139
                                                <option value="5">Thursdays</option>
140
                                                <option value="6">Fridays</option>
141
                                                <option value="7">Saturdays</option>
142
                                            </select>
143
                                        </li>
144
                                        <li class="radio" id="deleteType">
145
                                            <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label>
146
                                            <a href="#" class="helptext">[?]</a>
147
                                            <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if working day is selected.</div>
148
                                        </li>
149
                                        <li>
150
                                            <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' />
151
                                        </li>
152
                                        <li>
153
                                            <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' />
154
                                        </li>
155
                                        <li class="radio">
156
                                            <input type="radio" name="action" id="EditRadioButton" value="edit" checked/>
157
                                            <label for="EditRadioButton">Edit selected dates</label>
158
                                        </li>
159
                                        <li class="radio">
160
                                            <input type="radio" name="action" id="CopyRadioButton" value="copyDates" />
161
                                            <label for="CopyRadioButton">Copy to different dates</label>
162
                                        </li>
163
                                        <li class="CopyDatePanel">
164
                                            <label>From:</label>
165
                                            <input type="text" id="copyto_from_datepicker" size="20" class="datepicker"/>
166
                                            <label>To:</label>
167
                                            <input type="text" id="copyto_to_datepicker" size="20" class="datepicker"/>
168
                                        </li>
169
                                    </ol>
170
171
                                    <!-- These yyyy-mm-dd -->
172
                                    <input type="hidden" name="from_date" id='from_date'>
173
                                    <input type="hidden" name="to_date" id='to_date'>
174
                                    <input type="hidden" name="copyto_from" id='copyto_from'>
175
                                    <input type="hidden" name="copyto_to" id='copyto_to'>
176
                                    <input type="hidden" name="daysnumber" id='daysnumber'>
177
                                    <input type="hidden" name="local_today" id='local_today'>
178
179
                                    <fieldset class="action">
180
                                        <input type="submit" name="submit" value="Save" />
181
                                        <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
182
                                    </fieldset>
183
                                </fieldset>
184
                            </form>
185
                        </div>
186
                    </div>
187
188
                    <div class="col-sm-4">
189
                        <div class="help">
190
                            <h4>Hints</h4>
191
                            <ul>
192
                                <li>Search in the calendar the day you want to set as holiday.</li>
193
                                <li>Click the date to add or edit a holiday.</li>
194
                                <li>Specify how the holiday should repeat.</li>
195
                                <li>Click Save to finish.</li>
196
                                <li>PS:
197
                                    <ul>
198
                                        <li>Past dates cannot be changed</li>
199
                                        <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li>
200
                                    </ul>
201
                                </li>
202
                            </ul>
203
                            <h4>Key</h4>
204
                            <p>
205
                                <span class="key normalday">Working day</span>
206
                                <span class="key holiday">Unique holiday</span>
207
                                <span class="key repeatableweekly">Holiday repeating weekly</span>
208
                                <span class="key repeatableyearly">Holiday repeating yearly</span>
209
                                <span class="key float">Floating holiday</span>
210
                                <span class="key exception">Need validation</span>
211
                            </p>
212
                        </div>
213
214
                        <div id="holiday-list">
215
                            [% IF ( NEED_VALIDATION_HOLIDAYS ) %]
216
                            <h3>Need validation holidays</h3>
217
                            <table id="holidaysunique">
218
                                <thead>
219
                                    <tr>
220
                                        <th class="exception">Date</th>
221
                                        <th class="exception">Title</th>
222
                                    </tr>
223
                                </thead>
224
                                <tbody>
225
                                    [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %]
226
                                    <tr>
227
                                        <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>
228
                                        <td>[% need_validation_holiday.note | html %]</td>
229
                                    </tr>
230
                                    [% END %]
231
                                </tbody>
232
                            </table>
233
                            [% END %]
234
235
                            [% IF ( WEEKLY_HOLIDAYS ) %]
236
                            <h3>Weekly - Repeatable holidays</h3>
237
                            <table id="holidayweeklyrepeatable">
238
                                <thead>
239
                                    <tr>
240
                                        <th class="repeatableweekly">Day of week</th>
241
                                        <th class="repeatableweekly">Title</th>
242
                                    </tr>
243
                                </thead>
244
                                <tbody>
245
                                    [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %]
246
                                    <tr>
247
                                        <td>[% WEEK_DAYS_LOO.weekday | html %]</td>
248
                                        <td>[% WEEK_DAYS_LOO.note | html %]</td>
249
                                    </tr>
250
                                    [% END %]
251
                                </tbody>
252
                            </table>
253
                            [% END %]
254
255
                            [% IF ( REPEATABLE_HOLIDAYS ) %]
256
                            <h3>Yearly - Repeatable holidays</h3>
257
                            <table id="holidaysyearlyrepeatable">
258
                                <thead>
259
                                    <tr>
260
                                        [% IF ( dateformat == "metric" ) %]
261
                                        <th class="repeatableyearly">Day/month</th>
262
                                        [% ELSE %]
263
                                        <th class="repeatableyearly">Month/day</th>
264
                                        [% END %]
265
                                        <th class="repeatableyearly">Title</th>
266
                                    </tr>
267
                                </thead>
268
                                <tbody>
269
                                    [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %]
270
                                    <tr>
271
                                        [% IF ( dateformat == "metric" ) %]
272
                                        <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">[% DAY_MONTH_HOLIDAYS_LOO.day | html %]/[% DAY_MONTH_HOLIDAYS_LOO.month | html %]</span></td>
273
                                        [% ELSE %]
274
                                        <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">[% DAY_MONTH_HOLIDAYS_LOO.month | html %]/[% DAY_MONTH_HOLIDAYS_LOO.day | html %]</span></td>
275
                                        [% END %]
276
                                        <td>[% DAY_MONTH_HOLIDAYS_LOO.note | html %]</td>
277
                                    </tr>
278
                                    [% END %]
279
                                </tbody>
280
                            </table>
281
                            [% END %]
282
283
                            [% IF ( UNIQUE_HOLIDAYS ) %]
284
                            <h3>Unique holidays</h3>
285
                            <table id="holidaysunique">
286
                                <thead>
287
                                    <tr>
288
                                        <th class="holiday">Date</th>
289
                                        <th class="holiday">Title</th>
290
                                    </tr>
291
                                </thead>
292
                                <tbody>
293
                                    [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %]
294
                                    <tr>
295
                                        <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>
296
                                        <td>[% HOLIDAYS_LOO.note | html %]</td>
297
                                    </tr>
298
                                    [% END %]
299
                                </tbody>
300
                            </table>
301
                            [% END %]
302
303
                            [% IF ( FLOAT_HOLIDAYS ) %]
304
                            <h3>Floating holidays</h3>
305
                            <table id="holidaysunique">
306
                                <thead>
307
                                    <tr>
308
                                        <th class="float">Date</th>
309
                                        <th class="float">Title</th>
310
                                    </tr>
311
                                </thead>
312
                                <tbody>
313
                                    [% FOREACH float_holiday IN FLOAT_HOLIDAYS %]
314
                                    <tr>
315
                                        <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>
316
                                        <td>[% float_holiday.note | html %]</td>
317
                                    </tr>
318
                                    [% END %]
319
                                </tbody>
320
                            </table>
321
                            [% END %]
322
                        </div>
323
                    </div>
324
                </div>
325
326
            </main>
327
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
328
329
        <div class="col-sm-2 col-sm-pull-10">
330
            <aside>
331
                [% INCLUDE 'tools-menu.inc' %]
332
            </aside>
333
        </div> <!-- .col-sm-2.col-sm-pull-10 -->
334
    </div> <!-- /.row -->
335
336
[% MACRO jsinclude BLOCK %]
337
    [% INCLUDE 'calendar.inc' %]
338
    [% INCLUDE 'datatables.inc' %]
339
    [% Asset.js("lib/jquery/plugins/jquery-ui-timepicker-addon.min.js") | $raw %]
340
    [% Asset.js("js/tools-menu.js") | $raw %]
341
    <script>
342
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
343
344
        // Array containing all the information about each date in the calendar.
345
        var datesInfos = new Array();
346
        [% FOREACH date IN datesInfos %]
347
            datesInfos["[% date.date | html %]"] = {
348
                title : "[% date.note | html %]",
349
                outputdate : "[% date.outputdate | html %]",
350
                holiday_type:"[% date.holiday_type | html %]",
351
                open_hour: "[% date.open_hour | html %]",
352
                close_hour: "[% date.close_hour | html %]"
353
            };
354
        [% END %]
355
356
        /*
357
         * Displays the details of the selected date on a side panel
358
         */
359
        function showHoliday (date_obj, dateString, dayName, day, month, year, weekDay, title, holidayType) {
360
            $("#newHoliday").slideDown("fast");
361
            $("#copyHoliday").slideUp("fast");
362
            $('#newDaynameOutput').html(dayName);
363
            $('#newDayname').val(dayName);
364
            $('#newBranchNameOutput').html($("#branch :selected").text());
365
            $(".newHoliday").val($('#branch').val());
366
            $('#newDayOutput').html(day);
367
            $(".newHoliday #Day").val(day);
368
            $(".newHoliday #Month").val(month);
369
            $(".newHoliday #Year").val(year);
370
            $("#newMonthOutput").html(month);
371
            $("#newYearOutput").html(year);
372
            $(".newHoliday, #Weekday").val(weekDay);
373
374
            $('.newHoliday #title').val(title);
375
            $('#HolidayType').val(holidayType);
376
            $('#days_of_week option[value="'+ (weekDay + 1) +'"]').attr('selected', true);
377
            $('#openHour').val(datesInfos[dateString].open_hour);
378
            $('#closeHour').val(datesInfos[dateString].close_hour);
379
            $('#local_today').val(getSeparetedDate(new Date()).dateString);
380
381
            // This changes the label of the date type on the edit panel
382
            if (holidayType == 'W') {
383
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
384
            } else if (holidayType == 'R') {
385
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
386
            } else if (holidayType == 'F') {
387
                $("#holtype").attr("class","key float").html(_("Floating holiday"));
388
            } else if (holidayType == 'N') {
389
                $("#holtype").attr("class","key exception").html(_("Needs validation"));
390
            } else if (holidayType == 'E') {
391
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
392
            } else {
393
                $("#holtype").attr("class","key normalday").html(_("Working day "));
394
            }
395
396
            // Select the correct holiday type on the dropdown menu
397
            if (datesInfos[dateString].holiday_type !='') {
398
                var type = datesInfos[dateString].holiday_type;
399
                $('#holidayType option[value="'+ type +'"]').attr('selected', true)
400
            } else {
401
                $('#holidayType option[value="none"]').attr('selected', true)
402
            }
403
404
            // If it is a weekly or repeatable holiday show the option to delete the type
405
            if (datesInfos[dateString].holiday_type == 'W' || datesInfos[dateString].holiday_type == 'R') {
406
                $('#deleteType').show("fast");
407
            } else {
408
                $('#deleteType').hide("fast");
409
            }
410
411
            // This value is to disable and hide input when the date is in the past, because you can't edit it.
412
            var value = false;
413
            var today = new Date();
414
            today.setHours(0, 0, 0, 0);
415
            if (date_obj < today ) {
416
                $("#holtype").attr("class","key past-date").html(_("Past date"));
417
                $("#CopyRadioButton").attr("checked", "checked");
418
                value = true;
419
                $(".CopyDatePanel").toggle(value);
420
            }
421
            $("#title").prop('disabled', value);
422
            $("#holidayType select").prop('disabled', value);
423
            $("#openHour").prop('disabled', value);
424
            $("#closeHour").prop('disabled', value);
425
            $("#EditRadioButton").parent().toggle(!value);
426
        }
427
428
        // This function gives css classes to each kind of day
429
        function dateStatusHandler(date) {
430
            date = getSeparetedDate(date);
431
            var dateString = date.dateString;
432
            var today = new Date();
433
            today.setHours(0, 0, 0, 0);
434
435
            if (datesInfos[dateString] && datesInfos[dateString].holiday_type =='W') {
436
                return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)];
437
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'R') {
438
                return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)];
439
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'N') {
440
                return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)];
441
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'F') {
442
                return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)];
443
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'E') {
444
                return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)];
445
            } else {
446
                if (date.date_obj < today) {
447
                    return [true, "past-date", _("Past day")];
448
                }else {
449
                    return [true, "normalday", _("Normal day")];
450
                }
451
            }
452
        }
453
454
        /*
455
         * This function separate a given date object and returns an array containing all needed information about the date.
456
         */
457
        function getSeparetedDate(date) {
458
            var mydate = new Array();
459
            var day = (date.getDate() < 10 ? '0' : '') + date.getDate();
460
            var month = ((date.getMonth()+1) < 10 ? '0' : '') + (date.getMonth() +1);
461
            var year = date.getFullYear();
462
            var weekDay = date.getDay();
463
            // iso date string
464
            var dateString = year + '-' + month + '-' + day;
465
            mydate = {
466
                date_obj : date,
467
                dateString : dateString,
468
                weekDay: weekDay,
469
                year: year,
470
                month: month,
471
                day: day
472
            };
473
474
            return mydate;
475
        }
476
477
        /*
478
         * Validate the forms before sending them to the backend
479
         */
480
        function validateForm(form) {
481
            if (form =='newHoliday-form' && $('#CopyRadioButton').is(':checked')) {
482
                if ($('#copyto_from_datepicker').val() =='' || $('#copyto_to_datepicker').val() =='') {
483
                    alert("You have to pick a FROM and TO in the Copy to different dates.");
484
                    return false;
485
                } else if ($('#to_date_datepicker').val()) {
486
                    var from_DateFrom = new Date($("#jcalendar-container").datepicker("getDate"));
487
                    var from_DateTo = new Date($('#to_date_datepicker').datepicker("getDate"));
488
                    var to_DateFrom = new Date($('#copyto_from_datepicker').datepicker("getDate"));
489
                    var to_DateTo = new Date($('#copyto_to_datepicker').datepicker("getDate"));
490
491
                    var from_start = Math.round( from_DateFrom.getTime() / (3600*24*1000)); // days as integer from..
492
                    var from_end   = Math.round( from_DateTo.getTime() / (3600*24*1000));
493
                    var to_start = Math.round( to_DateFrom.getTime() / (3600*24*1000));
494
                    var to_end   = Math.round( to_DateTo.getTime() / (3600*24*1000));
495
496
                    var from_daysDiff = from_end - from_start +1;
497
                    var to_daysDiff = to_end - to_start + 1;
498
                    if (from_daysDiff == to_daysDiff) {
499
                        $('#daysnumber').val(to_daysDiff);
500
                        return true;
501
                    } else {
502
                        alert("You have to pick the same number of days if you choose 2 ranges");
503
                        return false;
504
                    }
505
                }
506
            } else if (form == 'copyCalendar-form') {
507
                if ($('#newBranch').val() =='') {
508
                    alert("Please select a copy to calendar.");
509
                    return false;
510
                } else {
511
                    return true;
512
                }
513
            } else {
514
                return true;
515
            }
516
        }
517
518
        function go_to_date(isoDate) {
519
            // I added the time to get around the timezone
520
            var date = getSeparetedDate(new Date(isoDate + " 00:00:00"));
521
            var day = date.day;
522
            var month = date.month;
523
            var year = date.year;
524
            var weekDay = date.weekDay;
525
            var dayName = weekdays[weekDay];
526
            var dateString = date.dateString;
527
            var date_obj = date.date_obj;
528
529
            $("#jcalendar-container").datepicker("setDate", date_obj);
530
            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type);
531
        }
532
533
        /*
534
         * Check if date range have the same opening, closing hours and holiday type if there's one.
535
         */
536
        function checkRange(date) {
537
            date = new Date(date);
538
            $('#to_date').val(getSeparetedDate(date).dateString);
539
            var fromDate = new Date($("#jcalendar-container").datepicker("getDate"));
540
            var sameHoliday =true;
541
            var sameOpenHours =true;
542
            var sameCloseHours =true;
543
544
            $('#days_of_week option[value="everyday"]').attr('selected', true);
545
            for (var i = fromDate; i <= date; i.setDate(i.getDate() + 1)) {
546
                var myDate1 = getSeparetedDate(i);
547
                var date1 = myDate1.dateString;
548
                var holidayType1 = datesInfos[date1].holiday_type;
549
                var open_hours1 = datesInfos[date1].open_hour;
550
                var close_hours1 = datesInfos[date1].close_hour;
551
                for (var j = fromDate; j <= date; j.setDate(j.getDate() + 1)) {
552
                    var myDate2 = getSeparetedDate(j);
553
                    var date2 = myDate2.dateString;
554
                    var holidayType2 = datesInfos[date2].holiday_type;
555
                    var open_hours2 = datesInfos[date2].open_hour;
556
                    var close_hours2 = datesInfos[date2].close_hour;
557
558
                    if (sameHoliday && holidayType1 != holidayType2) {
559
                        $('#holidayType option[value="empty"]').attr('selected', true);
560
                        sameHoliday=false;
561
                    }
562
                    if (sameOpenHours && (open_hours1 != open_hours2)) {
563
                        $('#openHour').val('');
564
                        sameOpenHours=false;
565
                    }
566
                    if (sameCloseHours && (close_hours1 != close_hours2)) {
567
                        $('#closeHour').val('');
568
                        sameCloseHours=false;
569
                    }
570
                }
571
                if (!sameOpenHours && !sameCloseHours && !sameHoliday) {
572
                    return false;
573
                }
574
            }
575
            return true;
576
        }
577
578
        $(document).ready(function() {
579
            $(".hint").hide();
580
            $("#days_of_week").hide();
581
            $("#deleteType").hide();
582
            $(".CopyDatePanel").hide();
583
584
            $("#branch").change(function() {
585
                var branch = $(this).find("option:selected").val();
586
                location.href = '/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate | html %]";
587
            });
588
589
            $("#holidayweeklyrepeatable>tbody>tr").each(function() {
590
                var first_td = $(this).find('td').first();
591
                first_td.html(weekdays[first_td.html()]);
592
            });
593
594
            $("a.helptext").click(function () {
595
                $(this).parent().find(".hint").toggle();
596
                return false;
597
            });
598
599
            $("form").on("submit", function() {
600
                return validateForm($(this).attr("id"));
601
            });
602
603
            // Set the correct coloring, default date and the date ranges for all datepickers
604
            [%    IF (dateformat == 'metric') %][% datepickerformat = 'dd/mm/yy' %]
605
            [% ELSIF (dateformat == 'us'    ) %][% datepickerformat = 'mm/dd/yy' %]
606
            [% ELSIF (dateformat == 'iso'   ) %][% datepickerformat = 'yy-mm-dd' %]
607
            [% ELSIF (dateformat == 'dmydot') %][% datepickerformat = 'dd.mm.yy' %]
608
            [% END %]
609
610
            $.datepicker.setDefaults({
611
                beforeShowDay: function(thedate) {
612
                    return dateStatusHandler(thedate);
613
                },
614
                defaultDate: new Date("[% keydate | html %]"),
615
                minDate: new Date("[% minDate | html %]"),
616
                maxDate: new Date("[% maxDate | html %]"),
617
                dateFormat: "[% datepickerformat | html %]"
618
            });
619
620
            // Main datepicker
621
            $("#jcalendar-container").datepicker({
622
                onSelect: function(dateText, inst) {
623
                    [% IF datesInfos %]
624
                        var date = getSeparetedDate($(this).datepicker("getDate"));
625
                        var weekDay = date.weekDay;
626
                        var dayName = weekdays[weekDay];
627
                        var dateString = date.dateString;
628
629
                        // set value of form hidden field
630
                        $('#from_date').val(dateText);
631
632
                        showHoliday(date.date_obj, dateString, dayName, date.day, date.month, date.year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type);
633
                    [% END %]
634
                },
635
            });
636
637
            $('#to_date_datepicker').datepicker();
638
            $("#to_date_datepicker").change(function() {
639
                checkRange($(this).datepicker("getDate"));
640
                $('#to_date').val(($(this).val()));
641
                if ($('#to_date_datepicker').val()) {
642
                    $('#days_of_week').show("fast");
643
                } else {
644
                    $('#days_of_week').hide("fast");
645
                }
646
            });
647
648
            // Datepickers for copy dates feature
649
            $('#copyto_from_datepicker').datepicker();
650
            $("#copyto_from_datepicker").change(function() {
651
                $('#copyto_from').val(($(this).val()));
652
            });
653
654
            $('#copyto_to_datepicker').datepicker();
655
            $("#copyto_to_datepicker").change(function() {
656
                $('#copyto_to').val(($(this).val()));
657
            });
658
659
            // Timepickers for open and close hours
660
            $('#openHour').timepicker({
661
                showOn : 'focus',
662
                timeFormat: 'HH:mm:ss',
663
                showSecond: false,
664
                stepMinute: 5,
665
            });
666
            $('#closeHour').timepicker({
667
                showOn : 'focus',
668
                timeFormat: 'HH:mm:ss',
669
                showSecond: false,
670
                stepMinute: 5,
671
            });
672
673
            $('.newHoliday input[type="radio"]').click(function() {
674
                if ($(this).attr("id") == "CopyRadioButton") {
675
                    $(".CopyToBranchPanel").hide('fast');
676
                    $(".CopyDatePanel").show('fast');
677
                } else if ($(this).attr("id") == "CopyToBranchRadioButton") {
678
                    $(".CopyDatePanel").hide('fast');
679
                    $(".CopyToBranchPanel").show('fast');
680
                } else {
681
                    $(".CopyDatePanel").hide('fast');
682
                    $(".CopyToBranchPanel").hide('fast');
683
                }
684
            });
685
686
            $(".hidePanel").on("click", function() {
687
                $(this).closest(".panel").slideUp("fast");
688
            });
689
690
            $("#deleteType_checkbox").on("change", function() {
691
                if ($("#deleteType_checkbox").is(':checked')) {
692
                    $('#holidayType option[value="none"]').attr('selected', true);
693
                }
694
            });
695
696
            $("#holidayType select").on("change", function() {
697
                if ($("#holidayType select").val() == "R") {
698
                    $('#days_of_week').hide("fast");
699
                } else if ($('#to_date_datepicker').val()) {
700
                    $('#days_of_week').show("fast");
701
                }
702
            });
703
        });
704
    </script>
705
[% END %]
706
707
<!-- the main div is closed in intranet-bottom.inc -->
708
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/holidays.tt (-660 lines)
Lines 1-660 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE KohaDates %]
4
[% USE Branches %]
5
[% SET footerjs = 1 %]
6
[% INCLUDE 'doc-head-open.inc' %]
7
<title>[% Branches.GetName( branch ) | html %] calendar &rsaquo; Tools &rsaquo; Koha</title>
8
[% INCLUDE 'doc-head-close.inc' %]
9
[% Asset.css("css/calendar.css") | $raw %]
10
</head>
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
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
18
    <ol>
19
        <li>
20
            <a href="/cgi-bin/koha/mainpage.pl">Home</a>
21
        </li>
22
        <li>
23
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
24
        </li>
25
        <li>
26
            <a href="#" aria-current="page">
27
                [% Branches.GetName( branch ) | html %] calendar
28
            </a>
29
        </li>
30
    </ol>
31
</nav>
32
[% END %]
33
34
<div class="main container-fluid">
35
    <div class="row">
36
        <div class="col-sm-10 col-sm-push-2">
37
            <main>
38
39
                <h1>[% Branches.GetName( branch ) | html %] calendar</h1>
40
41
                <div class="row">
42
                    <div class="col-sm-6">
43
                        <div class="page-section">
44
                            <label for="branch">Define the holidays for:</label>
45
                            <select id="branch" name="branch">
46
                                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
47
                            </select>
48
49
                            <div class="panel" id="showHoliday">
50
                                <form action="/cgi-bin/koha/tools/exceptionHolidays.pl" method="post">
51
                                    <input type="hidden" id="showHolidayType" name="showHolidayType" value="" />
52
                                    <fieldset class="brief">
53
                                        <h3>Edit this holiday</h3>
54
                                        <span id="holtype"></span>
55
                                        <ol>
56
                                            <li>
57
                                                <strong>Library:</strong> <span id="showBranchNameOutput"></span>
58
                                                <input type="hidden" id="showBranchName" name="showBranchName" />
59
                                            </li>
60
                                            <li>
61
                                                <strong>From date:</strong>
62
                                                <span id="showDaynameOutput"></span>,
63
                                                [% IF ( dateformat == "us" ) %]
64
                                                    <span id="showMonthOutput"></span>/<span id="showDayOutput"></span>/<span id="showYearOutput"></span>
65
                                                [% ELSIF ( dateformat == "metric") %]
66
                                                    <span id="showDayOutput"></span>/<span id="showMonthOutput"></span>/<span id="showYearOutput"></span>
67
                                                [% ELSIF ( dateformat == "dmydot") %]
68
                                                    <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>
69
                                                [% END %]
70
71
                                                <input type="hidden" id="showDayname" name="showDayname" />
72
                                                <input type="hidden" id="showWeekday" name="showWeekday" />
73
                                                <input type="hidden" id="showDay" name="showDay" />
74
                                                <input type="hidden" id="showMonth" name="showMonth" />
75
                                                <input type="hidden" id="showYear" name="showYear" />
76
                                            </li>
77
                                            <li class="dateinsert">
78
                                                <strong>To date: </strong>
79
                                                <input type="text" id="datecancelrange" name="datecancelrange" size="20" value="[% datecancelrange | html %]" class="flatpickr" />
80
                                            </li>
81
                                            <li>
82
                                                <label for="showTitle">Title: </label><input type="text" name="showTitle" id="showTitle" size="35" />
83
                                            </li>
84
                                            <!-- showTitle is necessary for exception radio button to work properly -->
85
                                            <li>
86
                                                <label for="showDescription">Description:</label>
87
                                                <textarea rows="2" cols="40" id="showDescription" name="showDescription"></textarea>
88
                                            </li>
89
                                            <li class="radio">
90
                                                <div class="exceptionPossibility" style="position:static">
91
                                                    <input type="radio" name="showOperation" id="showOperationExc" value="exception" /> <label for="showOperationExc">Generate an exception for this repeated holiday.</label>
92
                                                    <a href="#" class="helptext">[?]</a>
93
                                                    <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>
94
                                                </div>
95
                                            </li>
96
                                            <li class="radio">
97
                                                <div class="exceptionPossibility" style="position:static">
98
                                                    <input type="radio" name="showOperation" id="showOperationExcRange" value="exceptionrange" />
99
                                                    <label for="showOperationExcRange">Generate exceptions on a range of dates.</label>
100
                                                    <a href="#" class="helptext">[?]</a>
101
                                                    <div class="hint">You can make an exception on a range of dates repeated yearly.</div>
102
                                                </div>
103
                                            </li>
104
                                            <li class="radio">
105
                                                <input type="radio" name="showOperation" id="showOperationDel" value="delete" />
106
                                                <label for="showOperationDel" id="showOperationDelLabel">Delete this holiday</label>
107
                                                <a href="#" class="helptext">[?]</a>
108
                                                <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>
109
                                            </li>
110
                                            <li class="radio">
111
                                                <input type="radio" name="showOperation" id="showOperationDelRange" value="deleterange" /> <label for="showOperationDelRange" id="showOperationDelLabelRange">Delete the single holidays on a range</label>.
112
                                                <a href="#" class="helptext">[?]</a>
113
                                                <div class="hint">This will delete the single holidays rules only. The repeatable holidays and exceptions will not be deleted.</div>
114
                                            </li>
115
                                            <li class="radio">
116
                                                <input type="radio" name="showOperation" id="showOperationDelRangeRepeat" value="deleterangerepeat" /> <label for="showOperationDelRangeRepeat" id="showOperationDelLabelRangeRepeat">Delete the repeated holidays on a range</label>.
117
                                                <a href="#" class="helptext">[?]</a>
118
                                                <div class="hint">This will delete the repeated holidays rules only. The repeatable holidays will be deleted but not the exceptions.</div>
119
                                            </li>
120
                                            <li class="radio">
121
                                                <input type="radio" name="showOperation" id="showOperationDelRangeRepeatExcept" value="deleterangerepeatexcept" /> <label for="showOperationDelRangeRepeatExcept" id="showOperationDelLabelRangeRepeatExcept">Delete the exceptions on a range</label>.
122
                                                <a href="#" class="helptext">[?]</a>
123
                                                <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>
124
                                            </li>
125
                                            <li class="radio">
126
                                                <input type="radio" name="showOperation" id="showOperationEdit" value="edit" checked="checked" /> <label for="showOperationEdit">Edit this holiday</label>
127
                                                <a href="#" class="helptext">[?]</a>
128
                                                <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>
129
                                            <li class="checkbox">
130
                                                <input type="checkbox" name="allBranches" id="allBranches" />
131
                                                <label for="allBranches">Copy changes to all libraries</label>.
132
                                                <a href="#" class="helptext">[?]</a>
133
                                                <div class="hint">If checked, changes for this holiday will be copied to all libraries. If the holiday doesn't exists for a library, no change is made.</div>
134
                                            </li>
135
                                        </ol>
136
                                        <fieldset class="action">
137
                                            <input type="submit" name="submit" class="btn btn-primary" value="Save" />
138
                                            <a href="#" class="cancel hidePanel showHoliday">Cancel</a>
139
                                        </fieldset>
140
                                    </fieldset> <!-- /.brief -->
141
                                </form>
142
                            </div> <!-- /#showHoliday -->
143
144
                            <!-- Panel to deal with new holidays -->
145
                            <div class="panel" id="newHoliday">
146
                                <form action="/cgi-bin/koha/tools/newHolidays.pl" method="post">
147
                                    <fieldset class="brief">
148
                                        <h3>Add new holiday</h3>
149
                                        <ol>
150
                                            <li>
151
                                                <strong>Library:</strong>
152
                                                <span id="newBranchNameOutput"></span>
153
                                                <input type="hidden" id="newBranchName" name="newBranchName" />
154
                                            </li>
155
                                            <li>
156
                                                <strong>From date:</strong>
157
                                                <span id="newDaynameOutput"></span>,
158
159
                                                [% IF ( dateformat == "us" ) %]
160
                                                    <span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>
161
                                                [% ELSIF ( dateformat == "metric" ) %]
162
                                                    <span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>
163
                                                [% ELSIF ( dateformat == "dmydot" ) %]
164
                                                    <span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>
165
                                                [% ELSE %]
166
                                                    <span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>
167
                                                [% END %]
168
169
                                                <input type="hidden" id="newDayname" name="showDayname" />
170
                                                <input type="hidden" id="newWeekday" name="newWeekday" />
171
                                                <input type="hidden" id="newDay" name="newDay" />
172
                                                <input type="hidden" id="newMonth" name="newMonth" />
173
                                                <input type="hidden" id="newYear" name="newYear" />
174
                                            </li>
175
                                            <li class="dateinsert">
176
                                                <strong>To date: </strong>
177
                                                <input type="text" id="dateofrange" name="dateofrange" size="20" value="[% dateofrange | html %]" class="flatpickr" />
178
                                            </li>
179
                                            <li>
180
                                                <label for="title">Title: </label>
181
                                                <input type="text" name="newTitle" id="title" size="35" /></li>
182
                                            <li>
183
                                                <label for="newDescription">Description:</label>
184
                                                <textarea rows="2" cols="40" id="newDescription" name="newDescription"></textarea>
185
                                            </li>
186
                                            <li class="radio">
187
                                                <input type="radio" name="newOperation" id="newOperationOnce" value="holiday" checked="checked" />
188
                                                <label for="newOperationOnce">Holiday only on this day</label>.
189
                                                <a href="#" class="helptext">[?]</a>
190
                                                <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>
191
                                            </li>
192
                                            <li class="radio">
193
                                                <input type="radio" name="newOperation" id="newOperationDay" value="weekday" />
194
                                                <label for="newOperationDay">Holiday repeated every same day of the week</label>.
195
                                                <a href="#" class="helptext">[?]</a>
196
                                                <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>
197
                                            </li>
198
                                            <li class="radio">
199
                                                <input type="radio" name="newOperation" id="newOperationYear" value="repeatable" />
200
                                                <label for="newOperationYear">Holiday repeated yearly on the same date</label>.
201
                                                <a href="#" class="helptext">[?]</a>
202
                                                <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>
203
                                            </li>
204
                                            <li class="radio">
205
                                                <input type="radio" name="newOperation" id="newOperationField" value="holidayrange" />
206
                                                <label for="newOperationField">Holidays on a range</label>.
207
                                                <a href="#" class="helptext">[?]</a>
208
                                                <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>
209
                                            </li>
210
                                            <li class="radio">
211
                                                <input type="radio" name="newOperation" id="newOperationFieldyear" value="holidayrangerepeat" />
212
                                                <label for="newOperationFieldyear">Holidays repeated yearly on a range</label>.
213
                                                <a href="#" class="helptext">[?]</a>
214
                                                <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>
215
                                            </li>
216
                                            <li class="checkbox">
217
                                                <input type="checkbox" name="allBranches" id="allBranches" />
218
                                                <label for="allBranches">Copy to all libraries</label>.
219
                                                <a href="#" class="helptext">[?]</a>
220
                                                <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>
221
                                            </li>
222
                                        </ol>
223
                                        <fieldset class="action">
224
                                            <input type="submit" name="submit" class="btn btn-primary" value="Save" />
225
                                            <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
226
                                        </fieldset>
227
                                    </fieldset> <!-- /.brief -->
228
                                </form>
229
                            </div> <!-- /#newHoliday -->
230
231
                            <div id="calendar-container">
232
                                <h3>Calendar information</h3>
233
                                <span id="calendar-anchor"></span>
234
                            </div>
235
                            <div style="margin-top: 2em;">
236
                                <form action="copy-holidays.pl" method="post">
237
                                    <input type="hidden" name="from_branchcode" value="[% branch | html %]" />
238
                                    <label for="branchcode">Copy holidays to:</label>
239
                                    <select id="branchcode" name="branchcode">
240
                                        <option value=""></option>
241
                                        [% FOREACH l IN Branches.all() %]
242
                                            <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
243
                                        [% END %]
244
                                    </select>
245
                                    <input type="submit" class="btn btn-primary" value="Copy" />
246
                                </form>
247
                            </div>
248
                        </div> <!-- /.page-section -->
249
                    </div> <!-- /.col-sm-6 -->
250
251
                    <div class="col-sm-6">
252
                        <div class="page-section">
253
                            <div class="help">
254
                                <h4>Hints</h4>
255
                                <ul>
256
                                    <li>Search in the calendar the day you want to set as holiday.</li>
257
                                    <li>Click the date to add or edit a holiday.</li>
258
                                    <li>Enter a title and description for the holiday.</li>
259
                                    <li>Specify how the holiday should repeat.</li>
260
                                    <li>Click Save to finish.</li>
261
                                </ul>
262
                                <h4>Key</h4>
263
                                <p>
264
                                    <span class="key normalday">Working day</span>
265
                                    <span class="key holiday">Unique holiday</span>
266
                                    <span class="key repeatableweekly">Holiday repeating weekly</span>
267
                                    <span class="key repeatableyearly">Holiday repeating yearly</span>
268
                                    <span class="key exception">Holiday exception</span>
269
                                </p>
270
                            </div> <!-- /#help -->
271
272
                            <div id="holiday-list">
273
                                <!-- Exceptions First -->
274
                                <!--   this will probably always have the least amount of data -->
275
                                [% IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
276
                                    <h3>Exceptions</h3>
277
                                    <label class="controls">
278
                                        <input type="checkbox" name="show_past" id="show_past_holidayexceptions" class="show_past" />
279
                                        Show past entries
280
                                    </label>
281
                                    <table id="holidayexceptions">
282
                                        <thead>
283
                                            <tr>
284
                                                <th class="exception">Date</th>
285
                                                <th class="exception">Title</th>
286
                                                <th class="exception">Description</th>
287
                                            </tr>
288
                                        </thead>
289
                                        <tbody>
290
                                            [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
291
                                                <tr data-date="[% EXCEPTION_HOLIDAYS_LOO.DATE_SORT | html %]">
292
                                                    <td data-order="[% EXCEPTION_HOLIDAYS_LOO.DATE_SORT | html %]">
293
                                                        <a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% EXCEPTION_HOLIDAYS_LOO.DATE | uri %]">
294
                                                            [% EXCEPTION_HOLIDAYS_LOO.DATE | html %]
295
                                                        </a>
296
                                                    </td>
297
                                                    <td>[% EXCEPTION_HOLIDAYS_LOO.TITLE | html %]</td>
298
                                                    <td>[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
299
                                                </tr>
300
                                            [% END %]
301
                                        </tbody>
302
                                    </table> <!-- /#holidayexceptions -->
303
                                [% END # /IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
304
305
                                [% IF ( WEEK_DAYS_LOOP ) %]
306
                                    <h3>Weekly - Repeatable holidays</h3>
307
                                    <table id="holidayweeklyrepeatable">
308
                                        <thead>
309
                                            <tr>
310
                                                <th class="repeatableweekly">Day of week</th>
311
                                                <th class="repeatableweekly">Title</th>
312
                                                <th class="repeatableweekly">Description</th>
313
                                            </tr>
314
                                        </thead>
315
                                        <tbody>
316
                                            [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
317
                                                <tr>
318
                                                    <td>[% WEEK_DAYS_LOO.KEY | html %]</td>
319
                                                    <td>[% WEEK_DAYS_LOO.TITLE | html %]</td>
320
                                                    <td>[% WEEK_DAYS_LOO.DESCRIPTION | html %]</td>
321
                                                </tr>
322
                                            [% END %]
323
                                        </tbody>
324
                                    </table> <!-- /#holidayweeklyrepeatable -->
325
                                [% END # / IF ( WEEK_DAYS_LOOP ) %]
326
327
                                [% IF ( DAY_MONTH_HOLIDAYS_LOOP ) %]
328
                                    <h3>Yearly - Repeatable holidays</h3>
329
                                    <table id="holidaysyearlyrepeatable">
330
                                        <thead>
331
                                            <tr>
332
                                                [% IF ( dateformat == "metric" ) %]
333
                                                    <th class="repeatableyearly">Day/month</th>
334
                                                [% ELSE %]
335
                                                    <th class="repeatableyearly">Month/day</th>
336
                                                [% END %]
337
                                                <th class="repeatableyearly">Title</th>
338
                                                <th class="repeatableyearly">Description</th>
339
                                            </tr>
340
                                        </thead>
341
                                        <tbody>
342
                                            [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
343
                                                <tr>
344
                                                    <td data-order="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">
345
                                                        [% DAY_MONTH_HOLIDAYS_LOO.DATE | html %]
346
                                                    </td>
347
                                                    <td>[% DAY_MONTH_HOLIDAYS_LOO.TITLE | html %]</td>
348
                                                    <td>[% DAY_MONTH_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
349
                                                </tr>
350
                                            [% END %]
351
                                        </tbody>
352
                                    </table> <!-- /#holidaysyearlyrepeatable -->
353
                                [% END # /IF ( DAY_MONTH_HOLIDAYS_LOOP ) %]
354
355
                                [% IF ( HOLIDAYS_LOOP ) %]
356
                                    <h3>Unique holidays</h3>
357
                                    <label class="controls">
358
                                        <input type="checkbox" name="show_past" id="show_past_holidaysunique" class="show_past" />
359
                                        Show past entries
360
                                    </label>
361
                                    <table id="holidaysunique">
362
                                        <thead>
363
                                            <tr>
364
                                                <th class="holiday">Date</th>
365
                                                <th class="holiday">Title</th>
366
                                                <th class="holiday">Description</th>
367
                                            </tr>
368
                                        </thead>
369
                                        <tbody>
370
                                            [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
371
                                                <tr data-date="[% HOLIDAYS_LOO.DATE_SORT | html %]">
372
                                                    <td data-order="[% HOLIDAYS_LOO.DATE_SORT | html %]">
373
                                                        <a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% HOLIDAYS_LOO.DATE | uri %]">
374
                                                            [% HOLIDAYS_LOO.DATE | html %]
375
                                                        </a>
376
                                                    </td>
377
                                                    <td>[% HOLIDAYS_LOO.TITLE | html %]</td>
378
                                                    <td>[% HOLIDAYS_LOO.DESCRIPTION.replace('\\\r\\\n', '<br />') | html %]</td>
379
                                                </tr>
380
                                            [% END %]
381
                                        </tbody>
382
                                    </table> <!-- #holidaysunique -->
383
                                [% END # /IF ( HOLIDAYS_LOOP ) %]
384
                            </div> <!-- /#holiday-list -->
385
                        </div> <!-- /.page-section -->
386
                    </div> <!-- /.col-sm-6 -->
387
                </div> <!-- /.row -->
388
            </main>
389
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
390
391
        <div class="col-sm-2 col-sm-pull-10">
392
            <aside>
393
                [% INCLUDE 'tools-menu.inc' %]
394
            </aside>
395
        </div> <!-- .col-sm-2.col-sm-pull-10 -->
396
     </div> <!-- /.row -->
397
398
[% MACRO jsinclude BLOCK %]
399
    [% INCLUDE 'calendar.inc' %]
400
    [% INCLUDE 'datatables.inc' %]
401
    [% Asset.js("js/tools-menu.js") | $raw %]
402
    <script>
403
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
404
405
        /* Creates all the structures to deal with all different kinds of holidays */
406
        var week_days = new Array();
407
        var holidays = new Array();
408
        var holidates = new Array();
409
        var exception_holidays = new Array();
410
        var day_month_holidays = new Array();
411
        var hola= "[% code | html %]";
412
        [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
413
            week_days["[% WEEK_DAYS_LOO.KEY | html %]"] = {title:"[% WEEK_DAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% WEEK_DAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
414
        [% END %]
415
        [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
416
            holidates.push("[% HOLIDAYS_LOO.KEY | html %]");
417
            holidays["[% HOLIDAYS_LOO.KEY | html %]"] = {title:"[% HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
418
        [% END %]
419
        [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
420
            exception_holidays["[% EXCEPTION_HOLIDAYS_LOO.KEY | html %]"] = {title:"[% EXCEPTION_HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
421
        [% END %]
422
        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
423
            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 %]"};
424
        [% END %]
425
426
        function holidayOperation(formObject, opType) {
427
            var op = document.getElementsByName('operation');
428
            op[0].value = opType;
429
            formObject.submit();
430
        }
431
432
        // This function shows the "Show Holiday" panel //
433
        function showHoliday (exceptionPossibility, dayName, day, month, year, weekDay, title, description, holidayType) {
434
            $("#newHoliday").slideUp("fast");
435
            $("#showHoliday").slideDown("fast");
436
            $('#showDaynameOutput').html(dayName);
437
            $('#showDayname').val(dayName);
438
            $('#showBranchNameOutput').html($("#branch :selected").text());
439
            $('#showBranchName').val($("#branch").val());
440
            $('#showDayOutput').html(day);
441
            $('#showDay').val(day);
442
            $('#showMonthOutput').html(month);
443
            $('#showMonth').val(month);
444
            $('#showYearOutput').html(year);
445
            $('#showYear').val(year);
446
            $('#showDescription').val(description);
447
            $('#showWeekday:first').val(weekDay);
448
            $('#showTitle').val(title);
449
            $('#showHolidayType').val(holidayType);
450
451
            if (holidayType == 'exception') {
452
                $("#showOperationDelLabel").html(_("Delete this exception."));
453
                $("#holtype").attr("class","key exception").html(_("Holiday exception"));
454
            } else if(holidayType == 'weekday') {
455
                $("#showOperationDelLabel").html(_("Delete this holiday."));
456
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
457
            } else if(holidayType == 'daymonth') {
458
                $("#showOperationDelLabel").html(_("Delete this holiday."));
459
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
460
            } else {
461
                $("#showOperationDelLabel").html(_("Delete this holiday."));
462
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
463
            }
464
465
            if (exceptionPossibility == 1) {
466
                $(".exceptionPossibility").parent().show();
467
            } else {
468
                $(".exceptionPossibility").parent().hide();
469
            }
470
        }
471
472
        // This function shows the "Add Holiday" panel //
473
        function newHoliday (dayName, day, month, year, weekDay) {
474
            $("#showHoliday").slideUp("fast");
475
            $("#newHoliday").slideDown("fast");
476
            $("#newDaynameOutput").html(dayName);
477
            $("#newDayname").val(dayName);
478
            $("#newBranchNameOutput").html($('#branch :selected').text());
479
            $("#newBranchName").val($('#branch').val());
480
            $("#newDayOutput").html(day);
481
            $("#newDay").val(day);
482
            $("#newMonthOutput").html(month);
483
            $("#newMonth").val(month);
484
            $("#newYearOutput").html(year);
485
            $("#newYear").val(year);
486
            $("#newWeekday:first").val(weekDay);
487
        }
488
489
        function hidePanel(aPanelName) {
490
            $("#"+aPanelName).slideUp("fast");
491
        }
492
493
        function changeBranch () {
494
            var branch = $("#branch option:selected").val();
495
            location.href='/cgi-bin/koha/tools/holidays.pl?branch=' + branch + '&calendardate=' + "[% calendardate | html %]";
496
        }
497
498
        /**
499
        * Build settings to be passed to the formatDay function for each day in the calendar
500
        * @param  {object} dayElem - HTML node passed from Flatpickr
501
        * @return {void}
502
        */
503
        function dateStatusHandler( dayElem ) {
504
            var day = dayElem.dateObj.getDate();
505
            var month = dayElem.dateObj.getMonth() + 1;
506
            var year = dayElem.dateObj.getFullYear();
507
            var weekDay = dayElem.dateObj.getDay();
508
            var dayMonth = month + '/' + day;
509
            var dateString = year + '/' + month + '/' + day;
510
            if (exception_holidays[dateString] != null) {
511
                formatDay( [ "exception", _("Exception: %s").format(exception_holidays[dateString].title)], dayElem );
512
            } else if ( week_days[weekDay] != null ){
513
                formatDay( [ "repeatableweekly", _("Weekly holiday: %s").format(week_days[weekDay].title)], dayElem );
514
            } else if ( day_month_holidays[dayMonth] != null ) {
515
                formatDay( [ "repeatableyearly", _("Yearly holiday: %s").format(day_month_holidays[dayMonth].title)], dayElem );
516
            } else if (holidays[dateString] != null) {
517
                formatDay( [ "holiday", _("Single holiday: %s").format(holidays[dateString].title)], dayElem );
518
            } else {
519
                formatDay( [ "normalday", _("Normal day")], dayElem );
520
            }
521
        }
522
523
        /**
524
        * Adds style and title attribute to a day on the calendar
525
        * @param  {object} settings - span class attribute ([0]) and title attribute ([1])
526
        * @param  {node}   dayElem  - HTML node passed from Flatpickr
527
        * @return {void}
528
        */
529
        function formatDay( settings, dayElem ){
530
            $(dayElem).attr("title", settings[1]).addClass( settings[0]);
531
        }
532
533
        /**
534
        * Triggers an action based on a click on a calendar day: If a holiday exists on
535
        * that day it loads an edit form. If there is no existing holiday one can be created
536
        * @param  {object} calendar - a Date object corresponding to the clicked day
537
        * @return {void}
538
        */
539
        function dateChanged( calendar ) {
540
            var day = calendar.getDate();
541
            var month = calendar.getMonth() + 1;
542
            var year = calendar.getFullYear();
543
            var weekDay = calendar.getDay();
544
            var dayName = weekdays[weekDay];
545
            var dayMonth = month + '/' + day;
546
            var dateString = year + '/' + month + '/' + day;
547
            if (holidays[dateString] != null) {
548
                showHoliday(0, dayName, day, month, year, weekDay, holidays[dateString].title,     holidays[dateString].description, 'ymd');
549
            } else if (exception_holidays[dateString] != null) {
550
                showHoliday(0, dayName, day, month, year, weekDay, exception_holidays[dateString].title, exception_holidays[dateString].description, 'exception');
551
            } else if (week_days[weekDay] != null) {
552
                showHoliday(1, dayName, day, month, year, weekDay, week_days[weekDay].title,     week_days[weekDay].description, 'weekday');
553
            } else if (day_month_holidays[dayMonth] != null) {
554
                showHoliday(1, dayName, day, month, year, weekDay, day_month_holidays[dayMonth].title, day_month_holidays[dayMonth].description, 'daymonth');
555
            } else {
556
                newHoliday(dayName, day, month, year, weekDay);
557
            }
558
        };
559
560
        /* Custom table search configuration: If a table row
561
            has an "expired" class, hide it UNLESS the
562
            show_expired checkbox is checked */
563
        $.fn.dataTable.ext.search.push(
564
            function( settings, searchData, index, rowData, counter ) {
565
                var table = settings.nTable.id;
566
                var row = $(settings.aoData[index].nTr);
567
                if( row.hasClass("date_past") && !$("#show_past_" + table ).prop("checked") ){
568
                    return false;
569
                } else {
570
                    return true;
571
                }
572
            }
573
        );
574
575
        // Create current date variable
576
        var date = new Date();
577
        var datestring = date.toISOString().substring(0, 10);
578
579
        $(document).ready(function() {
580
581
            $(".hint").hide();
582
            $("#branch").change(function(){
583
                changeBranch();
584
            });
585
            $("#holidayweeklyrepeatable>tbody>tr").each(function(){
586
                var first_td = $(this).find('td').first();
587
                first_td.html(weekdays[first_td.html()]);
588
            });
589
            $("#holidayweeklyrepeatable").dataTable($.extend(true, {}, dataTablesDefaults, {
590
                "sDom": 't',
591
                "bPaginate": false
592
            }));
593
            var tables = $("#holidayexceptions, #holidaysyearlyrepeatable, #holidaysunique").DataTable($.extend(true, {}, dataTablesDefaults, {
594
                "sDom": 't',
595
                "bPaginate": false,
596
                "createdRow": function( row, data, dataIndex ) {
597
                    var holiday = $(row).data("date");
598
                    if( holiday < datestring ){
599
                        $(row).addClass("date_past");
600
                    }
601
                }
602
            }));
603
604
            $(".show_past").on("change", function(){
605
                tables.draw();
606
            });
607
608
            $("a.helptext").click(function(){
609
                $(this).parent().find(".hint").toggle(); return false;
610
            });
611
612
            const dateofrange = document.querySelector("#dateofrange")._flatpickr;
613
            const datecancelrange = document.querySelector("#datecancelrange")._flatpickr;
614
615
            $("#dateofrange").each(function () { this.value = "" });
616
            $("#datecancelrange").each(function () { this.value = "" });
617
618
            var maincalendar = $("#calendar-anchor").flatpickr({
619
                inline: true,
620
                onReady: function( selectedDates, dateStr, instance ){
621
                    // We do not want to display the 'close' icon in this case
622
                    $(instance.input).siblings('.flatpickr-input').hide();
623
                },
624
                onDayCreate: function( dObj, dStr, fp, dayElem ){
625
                    /* for each day on the calendar, get the
626
                      correct status information for the date */
627
                    dateStatusHandler( dayElem );
628
                },
629
                onChange: function( selectedDates, dateStr, instance ){
630
                    var fromdate = selectedDates[0];
631
                    var enddate = dateofrange.selectedDates[0];
632
633
                    dateChanged( fromdate );
634
635
                    dateofrange.set( 'defaultDate', fromdate );
636
                    dateofrange.set( 'minDate', fromdate );
637
638
                    if ( enddate != undefined ) {
639
                        if ( enddate < fromdate ) {
640
                            dateofrange.set("defaultDate", fromdate);
641
                            dateofrange.setDate(fromdate);
642
                        }
643
                    }
644
645
                },
646
                defaultDate: new Date("[% keydate | html %]")
647
            });
648
649
            $(".hidePanel").on("click",function(){
650
                if( $(this).hasClass("showHoliday") ){
651
                    hidePanel("showHoliday");
652
                } else {
653
                    hidePanel("newHoliday");
654
                }
655
            })
656
        });
657
    </script>
658
[% END %]
659
660
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +1 lines)
Lines 119-125 Link Here
119
                    [% END %]
119
                    [% END %]
120
                    <dl>
120
                    <dl>
121
                        [% IF ( CAN_user_tools_edit_calendar ) %]
121
                        [% IF ( CAN_user_tools_edit_calendar ) %]
122
                            <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
122
                            <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
123
                            <dd>Define days when the library is closed</dd>
123
                            <dd>Define days when the library is closed</dd>
124
                        [% END %]
124
                        [% END %]
125
125
(-)a/misc/cronjobs/add_days_discrete_calendar.pl (+165 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 exemples :
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
            }
124
        }
125
        $holiday_type = '' if $is_opened;
126
        $statement = $dbh->prepare($day_last_week);
127
        $statement->execute($newDay, $newDay);
128
        my ( $open_hour, $close_hour, $weekly_holiday_type, $weekly_note ) = $statement->fetchrow_array;
129
130
        # weekly repeatable holidays
131
        if ( $weekly_holiday_type && $weekly_holiday_type eq 'W' ) {
132
            $is_opened = 0;
133
            $holiday_type = $weekly_holiday_type unless $holiday_type;
134
            $note = $weekly_note unless $note;
135
        }
136
137
        my $data = {
138
            date       => output_pref( { dt => $newDay, dateformat => 'iso', timeformat => '24hr' }),
139
            branchcode => $branchCode,
140
        };
141
142
        $data->{is_opened}    = $is_opened    if ( defined $is_opened );
143
        $data->{holiday_type} = $holiday_type if ( defined $holiday_type );
144
        $data->{note}         = $note         if ( defined $note );
145
        $data->{open_hour}    = $open_hour    // "09:00:00";
146
        $data->{close_hour}   = $close_hour   // "17:00:00";
147
148
        my $calendar_date = $schema->resultset( "DiscreteCalendar" )->create( $data )->get_from_storage();
149
150
        if ( $debug && !$@ ) {
151
            warn "Added day " . $calendar_date->date
152
                . " to " . $calendar_date->branchcode
153
                . " is opened: " . $calendar_date->is_opened
154
                . ", holiday_type: " . $calendar_date->holiday_type
155
                . ", note: " . $calendar_date->note
156
                . ", open_hour: " . $calendar_date->open_hour
157
                . ", close_hour: " . $calendar_date->close_hour
158
                . " \n";
159
        } elsif ( $@ ) {
160
            warn "Failed to add day $newDay to $branchCode : $_\n";
161
        }
162
    }
163
}
164
# If everything went well we commit to the database
165
$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 208-214 cronlogaction({ action => 'End', info => "COMPLETED" }); Link Here
208
sub set_holiday {
208
sub set_holiday {
209
    my ( $branch, $dt ) = @_;
209
    my ( $branch, $dt ) = @_;
210
210
211
    my $calendar = Koha::Calendar->new( branchcode => $branch );
211
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
212
    return $calendar->is_holiday($dt);
212
    return $calendar->is_holiday($dt);
213
}
213
}
214
214
(-)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 458-466 elsif ( defined $text_filename ) { Link Here
458
}
458
}
459
459
460
foreach my $branchcode (@branches) {
460
foreach my $branchcode (@branches) {
461
    my $calendar;
462
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
461
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
463
        $calendar = Koha::Calendar->new( branchcode => $branchcode );
462
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
464
        if ( $calendar->is_holiday($date_to_run) ) {
463
        if ( $calendar->is_holiday($date_to_run) ) {
465
            next;
464
            next;
466
        }
465
        }
Lines 579-591 END_SQL Link Here
579
                my $days_between;
578
                my $days_between;
580
                if ( C4::Context->preference('OverdueNoticeCalendar') )
579
                if ( C4::Context->preference('OverdueNoticeCalendar') )
581
                {
580
                {
582
                    $days_between =
581
                    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
583
                      $calendar->days_between( dt_from_string($data->{date_due}),
582
                    $days_between = $calendar->days_between( dt_from_string($data->{date_due}), $date_to_run );
584
                        $date_to_run );
585
                }
583
                }
586
                else {
584
                else {
587
                    $days_between =
585
                    $days_between = $date_to_run->delta_days( dt_from_string($data->{date_due}) );
588
                      $date_to_run->delta_days( dt_from_string($data->{date_due}) );
589
                }
586
                }
590
                $days_between = $days_between->in_units('days');
587
                $days_between = $days_between->in_units('days');
591
                if ($triggered) {
588
                if ($triggered) {
Lines 667-675 END_SQL Link Here
667
                my $exceededPrintNoticesMaxLines = 0;
664
                my $exceededPrintNoticesMaxLines = 0;
668
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
665
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
669
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
666
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
670
                        $days_between =
667
                        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
671
                          $calendar->days_between(
668
                        $days_between = $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
672
                            dt_from_string( $item_info->{date_due} ), $date_to_run );
673
                    }
669
                    }
674
                    else {
670
                    else {
675
                        $days_between =
671
                        $days_between =
(-)a/misc/cronjobs/staticfines.pl (-3 / +5 lines)
Lines 32-42 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
36
use C4::Log qw( cronlogaction );
35
use C4::Log qw( cronlogaction );
37
use Getopt::Long qw( GetOptions );
36
use Getopt::Long qw( GetOptions );
38
use List::MoreUtils qw( none );
37
use List::MoreUtils qw( none );
39
use Koha::DateUtils qw( dt_from_string );
38
use Koha::DateUtils qw( dt_from_string output_pref );
39
use C4::Circulation;
40
use Koha::DiscreteCalendar qw();    # don't need any exports from Calendar
41
use C4::Biblio;
40
use Koha::Patrons;
42
use Koha::Patrons;
41
43
42
my $help    = 0;
44
my $help    = 0;
Lines 169-175 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) { Link Here
169
171
170
    my $calendar;
172
    my $calendar;
171
    unless ( defined( $calendars{$branchcode} ) ) {
173
    unless ( defined( $calendars{$branchcode} ) ) {
172
        $calendars{$branchcode} = C4::Calendar->new( branchcode => $branchcode );
174
        $calendars{$branchcode} = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
173
    }
175
    }
174
    $calendar = $calendars{$branchcode};
176
    $calendar = $calendars{$branchcode};
175
    my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
177
    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 335-341 sub GetWaitingHolds { Link Here
335
            }
335
            }
336
        );
336
        );
337
337
338
        my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
338
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $issue->{'site'}, days_mode => $daysmode });
339
339
340
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
340
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
341
341
(-)a/tools/copy-holidays.pl (-40 lines)
Lines 1-40 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 $dbh                 = C4::Context->dbh();
32
33
checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
34
35
my $branchcode          = $input->param('branchcode');
36
my $from_branchcode     = $input->param('from_branchcode');
37
38
C4::Calendar->new(branchcode => $from_branchcode)->copy_to_branch($branchcode) if $from_branchcode && $branchcode;
39
40
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=".($branchcode || $from_branchcode));
(-)a/tools/discrete_calendar.pl (+175 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
        debug => 1,
39
    }
40
);
41
42
my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
43
my $calendar = Koha::DiscreteCalendar->new({branchcode => $branch});
44
#alert the user that they are using the default calendar because they do not have a library set
45
my $no_branch_selected = $calendar->{no_branch_selected};
46
47
my $weekday = $input->param('day_of_week');
48
49
my $holiday_type = $input->param('holidayType');
50
my $allbranches = $input->param('allBranches');
51
52
my $title = $input->param('Title');
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
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate')); } || dt_from_string;
59
my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
60
61
if ($action eq 'copyBranch') {
62
    $calendar->copy_to_branch(scalar $input->param('newBranch'));
63
} elsif($action eq 'copyDates') {
64
    my $from_startDate = $input->param('from_date') ||'';
65
    my $from_endDate = $input->param('to_date') || '';
66
    my $to_startDate = $input->param('copyto_from') || '';
67
    my $to_endDate = $input->param('copyto_to') || '';
68
    my $local_today = $input->param('local_today');
69
    my $daysnumber = $input->param('daysnumber');
70
71
    $from_startDate = eval { dt_from_string(scalar $from_startDate) } if $from_startDate ne '';
72
    $from_endDate = eval { dt_from_string(scalar $from_endDate) } if $from_endDate ne '';
73
    $to_startDate = eval { dt_from_string(scalar $to_startDate) } if $to_startDate ne '';
74
    $to_endDate = eval { dt_from_string(scalar $to_endDate) } if $to_endDate ne '';
75
    $local_today = eval { dt_from_string( $local_today, 'iso') };
76
77
    unless ($@) {
78
        my $diff_from_today = $local_today - $to_startDate;
79
        if ( $diff_from_today->is_positive ) {
80
            $template->param(
81
                cannot_edit_past_dates => 1,
82
                error_date => $to_startDate
83
            );
84
        }
85
        else {
86
            $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
87
        }
88
    } else {
89
        $template->param( date_format_error => 1 );
90
    }
91
} elsif ($action eq 'edit') {
92
    my $openHour = $input->param('openHour');
93
    my $closeHour = $input->param('closeHour');
94
    my $startDate = $input->param('from_date');
95
    my $endDate = $input->param('to_date');
96
    my $deleteType = $input->param('deleteType') || 0;
97
    #Get today from javascript for a precise local time
98
    my $local_today = $input->param('local_today');
99
    $local_today = eval { dt_from_string( $local_today, 'iso') };
100
101
    $startDate = eval { dt_from_string(scalar $startDate) };
102
103
    unless ($@) {
104
        if($endDate ne '' ) {
105
            $endDate = eval { dt_from_string(scalar $endDate) };
106
        } else {
107
            $endDate = $startDate->clone();
108
        }
109
110
        $calendar->edit_holiday({
111
            title        => $title,
112
            weekday      => $weekday,
113
            holiday_type => $holiday_type,
114
            open_hour    => $openHour,
115
            close_hour   => $closeHour,
116
            start_date   => $startDate,
117
            end_date     => $endDate,
118
            delete_type  => $deleteType,
119
            today        => $local_today
120
        });
121
    } else {
122
        $template->param( date_format_error => 1 );
123
    }
124
}
125
126
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
127
128
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
129
$keydate =~ s/-/\//g;
130
131
# Set all the branches.
132
if ( C4::Context->only_my_library ) {
133
    $branch = C4::Context->userenv->{'branch'};
134
}
135
136
# Get all the holidays
137
138
#discrete_calendar weekly holidays
139
my @week_days = $calendar->get_week_days_holidays();
140
141
#discrete_calendar repeatable holidays
142
my @repeatable_holidays = $calendar->get_repeatable_holidays();
143
144
#discrete_calendar unique holidays
145
my @unique_holidays =$calendar->get_unique_holidays();
146
#discrete_calendar floating holidays
147
my @float_holidays =$calendar->get_float_holidays();
148
#discrete_caledar need validation holidays
149
my @need_validation_holidays =$calendar->get_need_validation_holidays();
150
151
#Calendar maximum date
152
my $minDate = $calendar->get_min_date();
153
154
#Calendar minimum date
155
my $maxDate = $calendar->get_max_date();
156
157
my @datesInfos = $calendar->get_dates_info();
158
159
$template->param(
160
    UNIQUE_HOLIDAYS          => \@unique_holidays,
161
    FLOAT_HOLIDAYS           => \@float_holidays,
162
    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
163
    REPEATABLE_HOLIDAYS      => \@repeatable_holidays,
164
    WEEKLY_HOLIDAYS          => \@week_days,
165
    calendardate             => $calendardate,
166
    keydate                  => $keydate,
167
    branch                   => $branch,
168
    minDate                  => $minDate,
169
    maxDate                  => $maxDate,
170
    datesInfos               => \@datesInfos,
171
    no_branch_selected       => $no_branch_selected,
172
);
173
174
# Shows the template with the real values replaced
175
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/exceptionHolidays.pl (-143 lines)
Lines 1-143 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 $dbh = C4::Context->dbh();
16
17
checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
18
19
20
our $branchcode = $input->param('showBranchName');
21
my $originalbranchcode  = $branchcode;
22
our $weekday = $input->param('showWeekday');
23
our $day = $input->param('showDay');
24
our $month = $input->param('showMonth');
25
our $year = $input->param('showYear');
26
our $title = $input->param('showTitle');
27
our $description = $input->param('showDescription');
28
our $holidaytype = $input->param('showHolidayType');
29
my $datecancelrange_dt = eval { dt_from_string( scalar $input->param('datecancelrange') ) };
30
my $calendardate = sprintf("%04d-%02d-%02d", $year, $month, $day);
31
our $showoperation = $input->param('showOperation');
32
my $allbranches = $input->param('allBranches');
33
34
$title || ($title = '');
35
if ($description) {
36
    $description =~ s/\r/\\r/g;
37
    $description =~ s/\n/\\n/g;
38
} else {
39
    $description = '';
40
}   
41
42
# We make an array with holiday's days
43
our @holiday_list;
44
if ($datecancelrange_dt){
45
            my $first_dt = DateTime->new(year => $year, month  => $month,  day => $day);
46
47
            for (my $dt = $first_dt->clone();
48
                $dt <= $datecancelrange_dt;
49
                $dt->add(days => 1) )
50
                {
51
                push @holiday_list, $dt->clone();
52
                }
53
}
54
55
if($allbranches) {
56
    my $libraries = Koha::Libraries->search;
57
    while ( my $library = $libraries->next ) {
58
        edit_holiday($showoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list);
59
    }
60
} else {
61
    edit_holiday($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list);
62
}
63
64
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
65
66
sub edit_holiday {
67
    ($showoperation, $branchcode, $weekday, $day, $month, $year, $title, $description, $holidaytype, @holiday_list) = @_;
68
    my $calendar = C4::Calendar->new(branchcode => $branchcode);
69
70
    if ($showoperation eq 'exception') {
71
        $calendar->insert_exception_holiday(day => $day,
72
                                            month => $month,
73
                                            year => $year,
74
                                            title => $title,
75
                                            description => $description);
76
    } elsif ($showoperation eq 'exceptionrange' ) {
77
            if (@holiday_list){
78
                foreach my $date (@holiday_list){
79
                    $calendar->insert_exception_holiday(
80
                        day         => $date->{local_c}->{day},
81
                        month       => $date->{local_c}->{month},
82
                        year       => $date->{local_c}->{year},
83
                        title       => $title,
84
                        description => $description
85
                        );
86
                }
87
            }
88
    } elsif ($showoperation eq 'edit') {
89
        if($holidaytype eq 'weekday') {
90
        $calendar->ModWeekdayholiday(weekday => $weekday,
91
                                    title => $title,
92
                                    description => $description);
93
        } elsif ($holidaytype eq 'daymonth') {
94
        $calendar->ModDaymonthholiday(day => $day,
95
                                        month => $month,
96
                                        title => $title,
97
                                        description => $description);
98
        } elsif ($holidaytype eq 'ymd') {
99
        $calendar->ModSingleholiday(day => $day,
100
                                    month => $month,
101
                                    year => $year,
102
                                    title => $title,
103
                                    description => $description);
104
        } elsif ($holidaytype eq 'exception') {
105
        $calendar->ModExceptionholiday(day => $day,
106
                                    month => $month,
107
                                    year => $year,
108
                                    title => $title,
109
                                    description => $description);
110
        }
111
    } elsif ($showoperation eq 'delete') {
112
        $calendar->delete_holiday(weekday => $weekday,
113
                                day => $day,
114
                                month => $month,
115
                                year => $year);
116
    }elsif ($showoperation eq 'deleterange') {
117
        if (@holiday_list){
118
            foreach my $date (@holiday_list){
119
                $calendar->delete_holiday_range(weekday => $weekday,
120
                                                day => $date->{local_c}->{day},
121
                                                month => $date->{local_c}->{month},
122
                                                year => $date->{local_c}->{year});
123
                }
124
        }
125
    }elsif ($showoperation eq 'deleterangerepeat') {
126
        if (@holiday_list){
127
            foreach my $date (@holiday_list){
128
            $calendar->delete_holiday_range_repeatable(weekday => $weekday,
129
                                            day => $date->{local_c}->{day},
130
                                            month => $date->{local_c}->{month});
131
            }
132
        }
133
    }elsif ($showoperation eq 'deleterangerepeatexcept') {
134
        if (@holiday_list){
135
            foreach my $date (@holiday_list){
136
            $calendar->delete_exception_holiday_range(weekday => $weekday,
137
                                            day => $date->{local_c}->{day},
138
                                            month => $date->{local_c}->{month},
139
                                            year => $date->{local_c}->{year});
140
            }
141
        }
142
    }
143
}
(-)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 (-145 lines)
Lines 1-144 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 $dbh                 = C4::Context->dbh();
33
34
checkauth($input, 0, {tools=> 'edit_calendar'}, 'intranet');
35
36
our $branchcode          = $input->param('newBranchName');
37
my $originalbranchcode  = $branchcode;
38
our $weekday             = $input->param('newWeekday');
39
our $day                 = $input->param('newDay');
40
our $month               = $input->param('newMonth');
41
our $year                = $input->param('newYear');
42
my $dateofrange         = $input->param('dateofrange');
43
our $title               = $input->param('newTitle');
44
our $description         = $input->param('newDescription');
45
our $newoperation        = $input->param('newOperation');
46
my $allbranches         = $input->param('allBranches');
47
48
49
my $first_dt = DateTime->new(year => $year, month  => $month,  day => $day);
50
my $end_dt   = eval { dt_from_string( $dateofrange ); };
51
52
my $calendardate = output_pref( { dt => $first_dt, dateonly => 1, dateformat => 'iso' } );
53
54
$title || ($title = '');
55
if ($description) {
56
	$description =~ s/\r/\\r/g;
57
	$description =~ s/\n/\\n/g;
58
} else {
59
	$description = '';
60
}
61
62
# We make an array with holiday's days
63
our @holiday_list;
64
if ($end_dt){
65
    for (my $dt = $first_dt->clone();
66
    $dt <= $end_dt;
67
    $dt->add(days => 1) )
68
    {
69
        push @holiday_list, $dt->clone();
70
    }
71
}
72
73
if($allbranches) {
74
    my $libraries = Koha::Libraries->search;
75
    while ( my $library = $libraries->next ) {
76
        add_holiday($newoperation, $library->branchcode, $weekday, $day, $month, $year, $title, $description);
77
    }
78
} else {
79
    add_holiday($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description);
80
}
81
82
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$originalbranchcode&calendardate=$calendardate");
83
84
#FIXME: move add_holiday() to a better place
85
sub add_holiday {
86
	($newoperation, $branchcode, $weekday, $day, $month, $year, $title, $description) = @_;  
87
	my $calendar = C4::Calendar->new(branchcode => $branchcode);
88
89
	if ($newoperation eq 'weekday') {
90
		unless ( $weekday && ($weekday ne '') ) { 
91
			# was dow calculated by javascript?  original code implies it was supposed to be.
92
			# if not, we need it.
93
			$weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7 unless($weekday);
94
		}
95
		unless($calendar->isHoliday($day, $month, $year)) {
96
			$calendar->insert_week_day_holiday(weekday => $weekday,
97
							           title => $title,
98
							           description => $description);
99
		}
100
	} elsif ($newoperation eq 'repeatable') {
101
		unless($calendar->isHoliday($day, $month, $year)) {
102
			$calendar->insert_day_month_holiday(day => $day,
103
	                                    month => $month,
104
							            title => $title,
105
							            description => $description);
106
		}
107
	} elsif ($newoperation eq 'holiday') {
108
		unless($calendar->isHoliday($day, $month, $year)) {
109
			$calendar->insert_single_holiday(day => $day,
110
	                                 month => $month,
111
						             year => $year,
112
						             title => $title,
113
						             description => $description);
114
		}
115
116
	} elsif ( $newoperation eq 'holidayrange' ) {
117
        if (@holiday_list){
118
            foreach my $date (@holiday_list){
119
                unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) {
120
                    $calendar->insert_single_holiday(
121
                        day         => $date->{local_c}->{day},
122
                        month       => $date->{local_c}->{month},
123
                        year        => $date->{local_c}->{year},
124
                        title       => $title,
125
                        description => $description
126
                    );
127
                }
128
            }
129
        }
130
    } elsif ( $newoperation eq 'holidayrangerepeat' ) {
131
        if (@holiday_list){
132
            foreach my $date (@holiday_list){
133
                unless ( $calendar->isHoliday( $date->{local_c}->{day}, $date->{local_c}->{month}, $date->{local_c}->{year} ) ) {
134
                    $calendar->insert_day_month_holiday(
135
                        day         => $date->{local_c}->{day},
136
                        month       => $date->{local_c}->{month},
137
                        title       => $title,
138
                        description => $description
139
                    );
140
                }
141
            }
142
        }
143
    }
144
}
145
- 

Return to bug 17015