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;
23
use Date::Calc qw( Date_to_Days 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 (-7 / +6 lines)
Lines 45-51 use Koha::Account; Link Here
45
use Koha::AuthorisedValues;
45
use Koha::AuthorisedValues;
46
use Koha::Biblioitems;
46
use Koha::Biblioitems;
47
use Koha::DateUtils;
47
use Koha::DateUtils;
48
use Koha::Calendar;
48
use Koha::DiscreteCalendar;
49
use Koha::Checkouts;
49
use Koha::Checkouts;
50
use Koha::Illrequests;
50
use Koha::Illrequests;
51
use Koha::Items;
51
use Koha::Items;
Lines 1355-1361 sub checkHighHolds { Link Here
1355
                branchcode   => $branchcode,
1355
                branchcode   => $branchcode,
1356
            }
1356
            }
1357
        );
1357
        );
1358
        my $calendar = Koha::Calendar->new( branchcode => $branchcode, days_mode => $daysmode );
1358
        my $calendar = Koha::DiscreteCalendar->new( branchcode => $branchcode, days_mode => $daysmode );
1359
1359
1360
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
1360
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branchcode, $borrower );
1361
1361
Lines 2509-2515 sub _calculate_new_debar_dt { Link Here
2509
        my $new_debar_dt;
2509
        my $new_debar_dt;
2510
        # Use the calendar or not to calculate the debarment date
2510
        # Use the calendar or not to calculate the debarment date
2511
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2511
        if ( C4::Context->preference('SuspensionsCalendar') eq 'noSuspensionsWhenClosed' ) {
2512
            my $calendar = Koha::Calendar->new(
2512
            my $calendar = Koha::DiscreteCalendar->new(
2513
                branchcode => $branchcode,
2513
                branchcode => $branchcode,
2514
                days_mode  => 'Calendar'
2514
                days_mode  => 'Calendar'
2515
            );
2515
            );
Lines 3704-3710 sub CalcDateDue { Link Here
3704
        else { # days
3704
        else { # days
3705
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3705
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3706
        }
3706
        }
3707
        my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
3707
        my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode );
3708
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3708
        $datedue = $calendar->addDuration( $datedue, $dur, $loanlength->{lengthunit} );
3709
        if ($loanlength->{lengthunit} eq 'days') {
3709
        if ($loanlength->{lengthunit} eq 'days') {
3710
            $datedue->set_hour(23);
3710
            $datedue->set_hour(23);
Lines 3743-3756 sub CalcDateDue { Link Here
3743
            }
3743
            }
3744
        }
3744
        }
3745
        if ( $daysmode ne 'Days' ) {
3745
        if ( $daysmode ne 'Days' ) {
3746
          my $calendar = Koha::Calendar->new( branchcode => $branch, days_mode => $daysmode );
3746
          my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch, days_mode => $daysmode );
3747
          if ( $calendar->is_holiday($datedue) ) {
3747
          if ( $calendar->is_holiday($datedue) ) {
3748
              # Don't return on a closed day
3748
              # Don't return on a closed day
3749
              $datedue = $calendar->prev_open_days( $datedue, 1 );
3749
              $datedue = $calendar->prev_open_days( $datedue )->set(hour => 23, minute => 59);
3750
          }
3750
          }
3751
        }
3751
        }
3752
    }
3752
    }
3753
3754
    return $datedue;
3753
    return $datedue;
3755
}
3754
}
3756
3755
(-)a/C4/HoldsQueue.pm (-2 / +3 lines)
Lines 33-38 use Koha::Items; Link Here
33
use Koha::Patrons;
33
use Koha::Patrons;
34
use Koha::Libraries;
34
use Koha::Libraries;
35
35
36
use Koha::DiscreteCalendar;
36
use List::Util qw(shuffle);
37
use List::Util qw(shuffle);
37
use List::MoreUtils qw(any);
38
use List::MoreUtils qw(any);
38
use Data::Dumper;
39
use Data::Dumper;
Lines 82-88 sub TransportCostMatrix { Link Here
82
        };
83
        };
83
84
84
        if ( !$ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
85
        if ( !$ignore_holds_queue_skip_closed && C4::Context->preference("HoldsQueueSkipClosed") ) {
85
            $calendars->{$from} ||= Koha::Calendar->new( branchcode => $from );
86
            $calendars->{$from} ||= Koha::DiscreteCalendar->new( branchcode => $from );
86
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
87
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
87
              $calendars->{$from}->is_holiday( $today );
88
              $calendars->{$from}->is_holiday( $today );
88
        }
89
        }
Lines 798-804 sub load_branches_to_pull_from { Link Here
798
    my $today = dt_from_string();
799
    my $today = dt_from_string();
799
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
800
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
800
        @branches_to_use = grep {
801
        @branches_to_use = grep {
801
            !Koha::Calendar->new( branchcode => $_ )
802
            !Koha::DiscreteCalendar->new({ branchcode => $_ })
802
              ->is_holiday( $today )
803
              ->is_holiday( $today )
803
        } @branches_to_use;
804
        } @branches_to_use;
804
    }
805
    }
(-)a/C4/Overdues.pm (-2 / +3 lines)
Lines 33-38 use C4::Accounts; Link Here
33
use C4::Log; # logaction
33
use C4::Log; # logaction
34
use C4::Debug;
34
use C4::Debug;
35
use Koha::DateUtils;
35
use Koha::DateUtils;
36
use Koha::DiscreteCalendar;
36
use Koha::Account::Lines;
37
use Koha::Account::Lines;
37
use Koha::Account::Offsets;
38
use Koha::Account::Offsets;
38
use Koha::Libraries;
39
use Koha::Libraries;
Lines 304-310 sub get_chargeable_units { Link Here
304
    my $charge_duration;
305
    my $charge_duration;
305
    if ($unit eq 'hours') {
306
    if ($unit eq 'hours') {
306
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
307
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
307
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
308
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
308
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
309
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
309
        } else {
310
        } else {
310
            $charge_duration = $date_returned->delta_ms( $date_due );
311
            $charge_duration = $date_returned->delta_ms( $date_due );
Lines 316-322 sub get_chargeable_units { Link Here
316
    }
317
    }
317
    else { # days
318
    else { # days
318
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
319
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
319
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
320
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
320
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
321
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
321
        } else {
322
        } else {
322
            $charge_duration = $date_returned->delta_days( $date_due );
323
            $charge_duration = $date_returned->delta_days( $date_due );
(-)a/C4/Reserves.pm (-2 / +2 lines)
Lines 34-43 use C4::Members::Messaging; Link Here
34
use C4::Members;
34
use C4::Members;
35
use Koha::Account::Lines;
35
use Koha::Account::Lines;
36
use Koha::Biblios;
36
use Koha::Biblios;
37
use Koha::Calendar;
38
use Koha::CirculationRules;
37
use Koha::CirculationRules;
39
use Koha::Database;
38
use Koha::Database;
40
use Koha::DateUtils;
39
use Koha::DateUtils;
40
use Koha::DiscreteCalendar;
41
use Koha::Hold;
41
use Koha::Hold;
42
use Koha::Holds;
42
use Koha::Holds;
43
use Koha::ItemTypes;
43
use Koha::ItemTypes;
Lines 939-945 sub CancelExpiredReserves { Link Here
939
    my $holds = Koha::Holds->search( $params );
939
    my $holds = Koha::Holds->search( $params );
940
940
941
    while ( my $hold = $holds->next ) {
941
    while ( my $hold = $holds->next ) {
942
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
942
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
943
943
944
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
944
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
945
945
(-)a/Koha/Calendar.pm (-555 lines)
Lines 1-555 Link Here
1
package Koha::Calendar;
2
3
use Modern::Perl;
4
5
use Carp;
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 qw( carp confess );
22
use Carp qw( carp confess );
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/DiscreteCalendar.pm (+1332 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;
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
    while (my $row = $branch_rs->next()) {
179
        $schema->resultset('DiscreteCalendar')->create({
180
            date         => $row->date(),
181
            branchcode   => $newBranch,
182
            is_opened    => $row->is_opened(),
183
            holiday_type => $row->holiday_type(),
184
            open_hour    => $row->open_hour(),
185
            close_hour   => $row->close_hour(),
186
        });
187
    }
188
189
}
190
191
=head2 get_date_info
192
193
    my $date = $calendar->get_date_info;
194
195
Returns a reference-to-hash representing a DiscreteCalendar date data object.
196
The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>,
197
C<$open_hour>, C<$close_hour> and C<$note>.
198
199
=cut
200
201
sub get_date_info {
202
    my ($self, $date) = @_;
203
    my $branchcode = $self->{branchcode};
204
    my $schema = Koha::Database->new->schema;
205
    my $dtf = $schema->storage->datetime_parser;
206
    #String dates for Database usage
207
    my $date_string = $dtf->format_datetime($date);
208
209
    my $rs = $schema->resultset('DiscreteCalendar')->search(
210
        {
211
            branchcode => $branchcode,
212
        },
213
        {
214
            select  => [ 'date', { DATE => 'date' } ],
215
            as      => [qw/ date date /],
216
            where   => \['DATE(?) = date', $date_string ],
217
            columns =>[ qw/ branchcode holiday_type open_hour close_hour note/]
218
        },
219
    );
220
    my $dateDTO;
221
    while (my $date = $rs->next()) {
222
        $dateDTO = {
223
            date         => $date->date(),
224
            branchcode   => $date->branchcode(),
225
            holiday_type => $date->holiday_type() ,
226
            open_hour    => $date->open_hour(),
227
            close_hour   => $date->close_hour(),
228
            note         => $date->note()
229
        };
230
    }
231
232
    return $dateDTO;
233
}
234
235
=head2 get_max_date
236
237
    my $maxDate = $calendar->get_max_date();
238
239
Returns the furthest date available in the databse of current branch.
240
241
=cut
242
243
sub get_max_date {
244
    my $self = shift;
245
    my $branchcode = $self->{branchcode};
246
    my $schema = Koha::Database->new->schema;
247
248
    my $rs = $schema->resultset('DiscreteCalendar')->search(
249
        {
250
            branchcode => $branchcode
251
        },
252
        {
253
            select => [{ MAX => 'date' } ],
254
            as     => [qw/ max /],
255
        }
256
    );
257
258
    return $rs->next()->get_column('max');
259
}
260
261
=head2 get_min_date
262
263
    my $minDate = $calendar->get_min_date();
264
265
Returns the oldest date available in the databse of current branch.
266
267
=cut
268
269
sub get_min_date {
270
    my $self = shift;
271
    my $branchcode = $self->{branchcode};
272
    my $schema = Koha::Database->new->schema;
273
274
    my $rs = $schema->resultset('DiscreteCalendar')->search(
275
        {
276
            branchcode => $branchcode
277
        },
278
        {
279
            select => [{ MIN => 'date' } ],
280
            as     => [qw/ min /],
281
        }
282
    );
283
284
    return $rs->next()->get_column('min');
285
}
286
287
=head2 get_unique_holidays
288
289
  my @unique_holidays = $calendar->get_unique_holidays();
290
291
Returns an array of all the date objects that are unique holidays.
292
293
=cut
294
295
sub get_unique_holidays {
296
    my $self = shift;
297
    my $exclude_past = shift || 1;
298
    my $branchcode = $self->{branchcode};
299
    my @unique_holidays;
300
    my $schema = Koha::Database->new->schema;
301
302
    my $rs = $schema->resultset('DiscreteCalendar')->search(
303
        {
304
            branchcode   => $branchcode,
305
            holiday_type => $HOLIDAYS->{EXCEPTION}
306
        },
307
        {
308
            select => [{ DATE => 'date' }, 'note' ],
309
            as     => [qw/ date note/],
310
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
311
        }
312
    );
313
    while (my $date = $rs->next()) {
314
        my $outputdate = dt_from_string($date->date(), 'iso');
315
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
316
        push @unique_holidays, {
317
            date       => $date->date(),
318
            outputdate => $outputdate,
319
            note       => $date->note()
320
        }
321
    }
322
323
    return @unique_holidays;
324
}
325
326
=head2 get_float_holidays
327
328
  my @float_holidays = $calendar->get_float_holidays();
329
330
Returns an array of all the date objects that are float holidays.
331
332
=cut
333
334
sub get_float_holidays {
335
    my $self = shift;
336
    my $exclude_past = shift || 1;
337
    my $branchcode = $self->{branchcode};
338
    my @float_holidays;
339
    my $schema = Koha::Database->new->schema;
340
341
    my $rs = $schema->resultset('DiscreteCalendar')->search(
342
        {
343
            branchcode   => $branchcode,
344
            holiday_type => $HOLIDAYS->{FLOAT}
345
        },
346
        {
347
            select => [{ DATE => 'date' }, 'note' ],
348
            as     => [qw/ date note/],
349
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
350
        }
351
    );
352
    while (my $date = $rs->next()) {
353
        my $outputdate = dt_from_string($date->date(), 'iso');
354
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
355
        push @float_holidays, {
356
            date        => $date->date(),
357
            outputdate  => $outputdate,
358
            note        => $date->note()
359
        }
360
    }
361
362
    return @float_holidays;
363
}
364
365
=head2 get_need_validation_holidays
366
367
  my @need_validation_holidays = $calendar->get_need_validation_holidays();
368
369
Returns an array of all the date objects that are float holidays in need of validation.
370
371
=cut
372
373
sub get_need_validation_holidays {
374
    my $self = shift;
375
    my $exclude_past = shift || 1;
376
    my $branchcode = $self->{branchcode};
377
    my @need_validation_holidays;
378
    my $schema = Koha::Database->new->schema;
379
380
    my $rs = $schema->resultset('DiscreteCalendar')->search(
381
        {
382
            branchcode   => $branchcode,
383
            holiday_type => $HOLIDAYS->{NEED_VALIDATION}
384
        },
385
        {
386
            select => [{ DATE => 'date' }, 'note' ],
387
            as     => [qw/ date note/],
388
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
389
        }
390
    );
391
    while (my $date = $rs->next()) {
392
        my $outputdate = dt_from_string($date->date(), 'iso');
393
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
394
        push @need_validation_holidays, {
395
            date        => $date->date(),
396
            outputdate  => $outputdate,
397
            note        => $date->note()
398
        }
399
    }
400
401
    return @need_validation_holidays;
402
}
403
404
=head2 get_repeatable_holidays
405
406
  my @repeatable_holidays = $calendar->get_repeatable_holidays();
407
408
Returns an array of all the date objects that are repeatable holidays.
409
410
=cut
411
412
sub get_repeatable_holidays {
413
    my $self = shift;
414
    my $exclude_past = shift || 1;
415
    my $branchcode = $self->{branchcode};
416
    my @repeatable_holidays;
417
    my $schema = Koha::Database->new->schema;
418
419
    my $rs = $schema->resultset('DiscreteCalendar')->search(
420
        {
421
            branchcode   => $branchcode,
422
            holiday_type => $HOLIDAYS->{'REPEATABLE'},
423
424
        },
425
        {
426
            select => \[ 'distinct DAY(date), MONTH(date), note'],
427
            as     => [qw/ day month note/],
428
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
429
        }
430
    );
431
432
    while (my $date = $rs->next()) {
433
        push @repeatable_holidays, {
434
            day => $date->get_column('day'),
435
            month => $date->get_column('month'),
436
            note => $date->note()
437
        };
438
    }
439
440
    return @repeatable_holidays;
441
}
442
443
=head2 get_week_days_holidays
444
445
  my @week_days_holidays = $calendar->get_week_days_holidays;
446
447
Returns an array of all the date objects that are weekly holidays.
448
449
=cut
450
451
sub get_week_days_holidays {
452
    my $self = shift;
453
    my $exclude_past = shift || 1;
454
    my $branchcode = $self->{branchcode};
455
    my @week_days;
456
    my $schema = Koha::Database->new->schema;
457
458
    my $rs = $schema->resultset('DiscreteCalendar')->search(
459
        {
460
            holiday_type => $HOLIDAYS->{WEEKLY},
461
            branchcode   => $branchcode,
462
        },
463
        {
464
            select   => [{ DAYOFWEEK => 'date'}, 'note'],
465
            as       => [qw/ weekday note /],
466
            distinct => 1,
467
            where    => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
468
        }
469
    );
470
471
    while (my $date = $rs->next()) {
472
        push @week_days, {
473
            weekday => ($date->get_column('weekday') -1),
474
            note    => $date->note()
475
        };
476
    }
477
478
    return @week_days;
479
}
480
481
=head2 edit_holiday
482
483
Modifies a date or a range of dates
484
485
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
486
487
C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range.
488
489
C<$holiday_type> Is the type of the holiday :
490
    E : Exception holiday, single day.
491
    F : Floating holiday, different day each year.
492
    N : Needs validation, copied float holiday from the past
493
    R : Repeatable holiday, repeated on same date.
494
    W : Weekly holiday, same day of the week.
495
496
C<$open_hour> Is the opening hour.
497
C<$close_hour> Is the closing hour.
498
C<$start_date> Is the start of the range of dates.
499
C<$end_date> Is the end of the range of dates.
500
C<$delete_type> Delete all
501
C<$today> Today based on the local date, using JavaScript.
502
503
=cut
504
505
sub edit_holiday {
506
    my $self = shift;
507
    my ($params) = @_;
508
509
    my $title        = $params->{title};
510
    my $weekday      = $params->{weekday} || '';
511
    my $holiday_type = $params->{holiday_type};
512
513
    my $start_date   = $params->{start_date};
514
    my $end_date     = $params->{end_date};
515
516
    my $open_hour    = $params->{open_hour} || '';
517
    my $close_hour   = $params->{close_hour} || '';
518
519
    my $delete_type  = $params->{delete_type} || undef;
520
    my $today        = $params->{today} || dt_from_string()->truncate( to => 'day' );
521
522
    my $branchcode = $self->{branchcode};
523
524
    # When override param is set, this function will allow past dates to be set as holidays,
525
    # otherwise it will not. This is meant to only be used for testing.
526
    my $override = $params->{override} || 0;
527
528
    my $schema = Koha::Database->new->schema;
529
    $schema->{AutoCommit} = 0;
530
    $schema->storage->txn_begin;
531
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
532
533
    #String dates for Database usage
534
    my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
535
    my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
536
    $today = $dtf->format_datetime($today->clone->truncate(to => 'day'));
537
    my %updateValues = (
538
        is_opened    => 0,
539
        note         => $title,
540
        holiday_type => $holiday_type,
541
    );
542
    $updateValues{open_hour} = $open_hour if $open_hour ne '';
543
    $updateValues{close_hour} = $close_hour if $close_hour ne '';
544
545
    if ($holiday_type eq $HOLIDAYS->{WEEKLY}) {
546
        #Update weekly holidays
547
        if ($start_date_string eq $end_date_string) {
548
            $end_date_string = $self->get_max_date();
549
        }
550
        my $rs = $schema->resultset('DiscreteCalendar')->search(
551
            {
552
                branchcode => $branchcode,
553
            },
554
            {
555
                where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string],
556
            }
557
        );
558
559
        while (my $date = $rs->next()) {
560
            $date->update(\%updateValues);
561
        }
562
    } elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) {
563
        #Update Exception Float and Needs Validation holidays
564
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
565
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
566
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
567
        }
568
        $where->{date}{'>='} = $today unless $override;
569
570
        my $rs = $schema->resultset('DiscreteCalendar')->search(
571
            {
572
                branchcode => $branchcode,
573
            },
574
            {
575
                where => $where,
576
            }
577
        );
578
        while (my $date = $rs->next()) {
579
            $date->update(\%updateValues);
580
        }
581
582
    } elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) {
583
        #Update repeatable holidays
584
        my $parser = DateTime::Format::Strptime->new(
585
           pattern  => '%m-%d',
586
           on_error => 'croak',
587
        );
588
        #Format the dates to have only month-day ex: 01-04 for January 4th
589
        $start_date = $parser->format_datetime($start_date);
590
        $end_date = $parser->format_datetime($end_date);
591
        my $where = { -and => [ \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] };
592
        push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override;
593
        my $rs = $schema->resultset('DiscreteCalendar')->search(
594
            {
595
                branchcode => $branchcode,
596
            },
597
            {
598
                where => $where,
599
            }
600
        );
601
        while (my $date = $rs->next()) {
602
            $date->update(\%updateValues);
603
        }
604
605
    } else {
606
        #Update date(s)/Remove holidays
607
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
608
        if ($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday') {
609
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
610
        }
611
        $where->{date}{'>='} = $today unless $override;
612
613
        my $rs = $schema->resultset('DiscreteCalendar')->search(
614
            {
615
                branchcode => $branchcode,
616
            },
617
            {
618
                where => $where,
619
            }
620
        );
621
        #If none, the date(s) will be normal days, else,
622
        if ($holiday_type eq 'none') {
623
            $updateValues{holiday_type} ='';
624
            $updateValues{is_opened} =1;
625
        } else {
626
            delete $updateValues{holiday_type};
627
            delete $updateValues{is_opened};
628
        }
629
630
        while (my $date = $rs->next()) {
631
            if ($delete_type) {
632
                if ($date->holiday_type() eq $HOLIDAYS->{WEEKLY}) {
633
                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
634
                } elsif ($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}) {
635
                    $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today);
636
                }
637
            } else {
638
                $date->update(\%updateValues);
639
            }
640
        }
641
    }
642
    $schema->storage->txn_commit;
643
    $schema->{AutoCommit} = 1;
644
}
645
646
=head2 remove_weekly_holidays
647
648
    $calendar->remove_weekly_holidays($weekday, $updateValues, $today);
649
650
Removes a weekly holiday and updates the days' parameters
651
C<$weekday> is the weekday to un-holiday
652
C<$updatevalues> is hashref containing the new parameters
653
C<$today> is today's date
654
655
=cut
656
657
sub remove_weekly_holidays {
658
    my ($self, $weekday, $updateValues, $today) = @_;
659
    my $branchcode = $self->{branchcode};
660
    my $schema = Koha::Database->new->schema;
661
662
    my $rs = $schema->resultset('DiscreteCalendar')->search(
663
        {
664
            branchcode   => $branchcode,
665
            is_opened    => 0,
666
            holiday_type => $HOLIDAYS->{WEEKLY}
667
        },
668
        {
669
            where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]},
670
        }
671
    );
672
673
    while (my $date = $rs->next()) {
674
        $date->update($updateValues);
675
    }
676
}
677
678
=head2 remove_repeatable_holidays
679
680
    $calendar->remove_repeatable_holidays($startDate, $endDate, $today);
681
682
Removes a repeatable holiday and updates the days' parameters
683
C<$startDatey> is the start date of the repeatable holiday
684
C<$endDate> is the end date of the repeatble holiday
685
C<$updatevalues> is hashref containing the new parameters
686
C<$today> is today's date
687
688
=cut
689
690
sub remove_repeatable_holidays {
691
    my ($self, $startDate, $endDate, $updateValues, $today) = @_;
692
    my $branchcode = $self->{branchcode};
693
    my $schema = Koha::Database->new->schema;
694
    my $parser = DateTime::Format::Strptime->new(
695
        pattern  => '%m-%d',
696
        on_error => 'croak',
697
    );
698
    #Format the dates to have only month-day ex: 01-04 for January 4th
699
    $startDate = $parser->format_datetime($startDate);
700
    $endDate = $parser->format_datetime($endDate);
701
702
    my $rs = $schema->resultset('DiscreteCalendar')->search(
703
        {
704
            branchcode   => $branchcode,
705
            is_opened    => 0,
706
            holiday_type => $HOLIDAYS->{REPEATABLE},
707
        },
708
        {
709
            where => { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date, '\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]},
710
        }
711
    );
712
713
    while (my $date = $rs->next()) {
714
        $date->update($updateValues);
715
    }
716
}
717
718
=head2 copy_to_branch
719
720
  $calendar->copy_to_branch($branch2);
721
722
Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self>
723
but not in C<$branch2>
724
725
C<$branch2> the branch to copy into
726
727
=cut
728
729
sub copy_to_branch {
730
    my ($self, $newBranch) =@_;
731
    my $branchcode = $self->{branchcode};
732
    my $schema = Koha::Database->new->schema;
733
734
    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
735
        {
736
            branchcode => $branchcode
737
        },
738
        {
739
            columns => [ qw/ date is_opened note holiday_type open_hour close_hour /]
740
        }
741
    );
742
    while (my $copyDate = $copyFrom->next()) {
743
        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
744
            {
745
                branchcode => $newBranch,
746
                date       => $copyDate->date(),
747
            },
748
            {
749
                columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /]
750
            }
751
        );
752
        #if the date does not exist in the copyTO branch, than skip it.
753
        if ($copyTo->count ==0) {
754
            next;
755
        }
756
        $copyTo->next()->update({
757
            is_opened    => $copyDate->is_opened(),
758
            holiday_type => $copyDate->holiday_type(),
759
            note         => $copyDate->note(),
760
            open_hour    => $copyDate->open_hour(),
761
            close_hour   => $copyDate->close_hour()
762
        });
763
    }
764
}
765
766
=head2 is_opened
767
768
    $calendar->is_opened($date)
769
770
Returns whether the library is open on C<$date>
771
772
=cut
773
774
sub is_opened {
775
    my($self, $date) = @_;
776
    my $branchcode = $self->{branchcode};
777
    my $schema = Koha::Database->new->schema;
778
    my $dtf = $schema->storage->datetime_parser;
779
    $date= $dtf->format_datetime($date);
780
    #if the date is not found
781
    my $is_opened = -1;
782
    my $rs = $schema->resultset('DiscreteCalendar')->search(
783
        {
784
            branchcode => $branchcode,
785
        },
786
        {
787
            where => \['date = DATE(?)', $date]
788
        }
789
    );
790
    $is_opened = $rs->next()->is_opened() if $rs->count() != 0;
791
792
    return $is_opened;
793
}
794
795
=head2 is_holiday
796
797
    $calendar->is_holiday($date)
798
799
Returns whether C<$date> is a holiday or not
800
801
=cut
802
803
sub is_holiday {
804
    my($self, $date) = @_;
805
    my $branchcode = $self->{branchcode};
806
    my $schema = Koha::Database->new->schema;
807
    my $dtf = $schema->storage->datetime_parser;
808
    $date= $dtf->format_datetime($date);
809
    #if the date is not found
810
    my $isHoliday = -1;
811
    my $rs = $schema->resultset('DiscreteCalendar')->search(
812
        {
813
            branchcode => $branchcode,
814
        },
815
        {
816
            where => \['date = DATE(?)', $date]
817
        }
818
    );
819
820
    if ($rs->count() != 0) {
821
        $isHoliday = ($rs->first()->is_opened() ? 0 : 1);
822
    }
823
824
    return $isHoliday;
825
}
826
827
=head2 copy_holiday
828
829
  $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
830
831
Copies a holiday's parameters from a range to a new range
832
C<$from_startDate> the source holiday's start date
833
C<$from_endDate> the source holiday's end date
834
C<$to_startDate> the destination holiday's start date
835
C<$to_endDate> the destination holiday's end date
836
C<$daysnumber> the number of days in the range.
837
838
Both ranges should have the same number of days in them.
839
840
=cut
841
842
sub copy_holiday {
843
    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
844
    my $branchcode = $self->{branchcode};
845
    my $copyFromType = $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
846
    my $schema = Koha::Database->new->schema;
847
    my $dtf = $schema->storage->datetime_parser;
848
849
    if ($copyFromType eq 'oneDay') {
850
        my $where;
851
        $to_startDate = $dtf->format_datetime($to_startDate);
852
        if ($to_startDate && $to_endDate) {
853
            $to_endDate = $dtf->format_datetime($to_endDate);
854
            $where = { date => { -between => [$to_startDate, $to_endDate]}};
855
        } else {
856
            $where = { date => $to_startDate };
857
        }
858
859
        $from_startDate = $dtf->format_datetime($from_startDate);
860
        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
861
            {
862
                branchcode => $branchcode,
863
                date       => $from_startDate
864
            }
865
        );
866
        my $toDates = $schema->resultset('DiscreteCalendar')->search(
867
            {
868
                branchcode => $branchcode,
869
            },
870
            {
871
                where => $where
872
            }
873
        );
874
875
        my $copyDate = $fromDate->next();
876
        while (my $date = $toDates->next()) {
877
            $date->update({
878
                is_opened    => $copyDate->is_opened(),
879
                holiday_type => $copyDate->holiday_type(),
880
                note         => $copyDate->note(),
881
                open_hour    => $copyDate->open_hour(),
882
                close_hour   => $copyDate->close_hour()
883
            })
884
        }
885
886
    } else {
887
        my $endDate = dt_from_string($from_endDate);
888
        $to_startDate = $dtf->format_datetime($to_startDate);
889
        $to_endDate = $dtf->format_datetime($to_endDate);
890
        if ($daysnumber == 7) {
891
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
892
                my $formatedDate = $dtf->format_datetime($tempDate);
893
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
894
                    {
895
                        branchcode => $branchcode,
896
                        date       => $formatedDate,
897
                    },
898
                    {
899
                        select  => [{ DAYOFWEEK => 'date' }],
900
                        as      => [qw/ weekday /],
901
                        columns =>[ qw/ holiday_type note open_hour close_hour note/]
902
                    }
903
                );
904
                my $copyDate = $fromDate->next();
905
                my $weekday = $copyDate->get_column('weekday');
906
907
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
908
                    {
909
                        branchcode => $branchcode,
910
911
                    },
912
                    {
913
                        where => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday},
914
                    }
915
                );
916
                my $copyToDate = $toDate->next();
917
                $copyToDate->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 $to_startDate = dt_from_string($to_startDate);
928
            my $to_endDate = dt_from_string($to_endDate);
929
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)) {
930
                my $from_formatedDate = $dtf->format_datetime($tempDate);
931
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
932
                    {
933
                        branchcode => $branchcode,
934
                        date       => $from_formatedDate,
935
                    },
936
                    {
937
                        order_by => { -asc => 'date' }
938
                    }
939
                );
940
                my $to_formatedDate = $dtf->format_datetime($to_startDate);
941
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
942
                    {
943
                        branchcode => $branchcode,
944
                        date       => $to_formatedDate
945
                    },
946
                    {
947
                        order_by => { -asc => 'date' }
948
                    }
949
                );
950
                my $copyDate = $fromDate->next();
951
                $toDate->next()->update({
952
                    is_opened    => $copyDate->is_opened(),
953
                    holiday_type => $copyDate->holiday_type(),
954
                    note         => $copyDate->note(),
955
                    open_hour    => $copyDate->open_hour(),
956
                    close_hour   => $copyDate->close_hour()
957
                });
958
                $to_startDate->add(days =>1);
959
            }
960
        }
961
962
963
    }
964
}
965
966
=head2 days_between
967
968
   $cal->days_between( $start_date, $end_date )
969
970
Calculates the number of days the library is opened between C<$start_date> and C<$end_date>
971
972
=cut
973
974
sub days_between {
975
    my ($self, $start_date, $end_date, ) = @_;
976
    my $branchcode = $self->{branchcode};
977
978
    if ( $start_date->compare($end_date) > 0 ) {
979
        # swap dates
980
        ($start_date, $end_date) = ($end_date, $start_date);
981
    }
982
983
    my $schema = Koha::Database->new->schema;
984
    my $dtf = $schema->storage->datetime_parser;
985
    $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
986
    $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
987
988
    my $days_between = $schema->resultset('DiscreteCalendar')->search(
989
        {
990
            branchcode => $branchcode,
991
            is_opened  => 1,
992
        },
993
        {
994
            where => \['date >= date(?) AND date < date(?)', $start_date, $end_date]
995
        }
996
    );
997
998
    return DateTime::Duration->new( days => $days_between->count());
999
}
1000
1001
=head2 next_open_day
1002
1003
   $open_date = $self->next_open_day($base_date);
1004
1005
Returns a string representing the next day the library is open, starting from C<$base_date>
1006
1007
=cut
1008
1009
sub next_open_day {
1010
    my ( $self, $date ) = @_;
1011
    my $branchcode = $self->{branchcode};
1012
    my $schema = Koha::Database->new->schema;
1013
    my $dtf = $schema->storage->datetime_parser;
1014
    my $formatted_date = $dtf->format_datetime($date);
1015
1016
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1017
        {
1018
            branchcode => $branchcode,
1019
            is_opened  => 1,
1020
        },
1021
        {
1022
            where    => \['date > date(?)', $formatted_date],
1023
            order_by => { -asc => 'date' },
1024
            rows     => 1
1025
        }
1026
    );
1027
1028
    my $next = $rs->next();
1029
    unless ( $next ) {
1030
        warn "No next opened date in calendar. Reduced to current date: $formatted_date.";
1031
        return $date;
1032
    }
1033
    return dt_from_string( $next->date(), 'iso');
1034
}
1035
1036
=head2 prev_open_day
1037
1038
   $open_date = $self->prev_open_day($base_date);
1039
1040
Returns a string representing the closest previous day the library was open, starting from C<$base_date>
1041
1042
=cut
1043
1044
sub prev_open_day {
1045
    my ( $self, $date ) = @_;
1046
    my $branchcode = $self->{branchcode};
1047
    my $schema = Koha::Database->new->schema;
1048
    my $dtf = $schema->storage->datetime_parser;
1049
    my $formatted_date = $dtf->format_datetime($date);
1050
1051
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1052
        {
1053
            branchcode => $branchcode,
1054
            is_opened  => 1,
1055
        },
1056
        {
1057
            where    => \['date < date(?)', $formatted_date],
1058
            order_by => { -desc => 'date' },
1059
            rows     => 1
1060
        }
1061
    );
1062
1063
    my $prev = $rs->next();
1064
    unless ( $prev ) {
1065
        warn "No previous opened date in calendar. Reduced to current date: $formatted_date.";
1066
        return $date;
1067
    }
1068
    return dt_from_string( $prev->date(), 'iso');
1069
}
1070
1071
=head2 days_forward
1072
1073
    $fwrd_date = $calendar->days_forward($start, $count)
1074
1075
Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed.
1076
1077
=cut
1078
1079
sub days_forward {
1080
    my $self = shift;
1081
    my $start_dt = shift;
1082
    my $num_days = shift;
1083
1084
    return $start_dt unless $num_days > 0;
1085
1086
    my $base_dt = $start_dt->clone();
1087
1088
    while ($num_days--) {
1089
        $base_dt = $self->next_open_day($base_dt);
1090
    }
1091
1092
    return $base_dt;
1093
}
1094
1095
=head2 hours_between
1096
1097
    $hours = $calendar->hours_between($start_dt, $end_dt)
1098
1099
Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise
1100
version, which simply calculates the number of day times 24. To take opening hours into account
1101
see C<open_hours_between>/
1102
1103
=cut
1104
1105
sub hours_between {
1106
    my ($self, $start_dt, $end_dt) = @_;
1107
    my $branchcode = $self->{branchcode};
1108
    my $schema = Koha::Database->new->schema;
1109
    my $dtf = $schema->storage->datetime_parser;
1110
    my $start_date = $start_dt->clone();
1111
    my $end_date = $end_dt->clone();
1112
    my $duration = $end_date->delta_ms($start_date);
1113
    $start_date->truncate( to => 'day' );
1114
    $end_date->truncate( to => 'day' );
1115
1116
    # NB this is a kludge in that it assumes all days are 24 hours
1117
    # However for hourly loans the logic should be expanded to
1118
    # take into account open/close times then it would be a duration
1119
    # of library open hours
1120
    my $skipped_days = 0;
1121
    $start_date = $dtf->format_datetime($start_date);
1122
    $end_date = $dtf->format_datetime($end_date);
1123
    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
1124
        {
1125
            branchcode => $branchcode,
1126
            is_opened  => 0
1127
        },
1128
        {
1129
            where => {date => {-between => [$start_date, $end_date]}},
1130
        }
1131
    );
1132
1133
    if ($skipped_days = $hours_between->count()) {
1134
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
1135
    }
1136
1137
    return $duration;
1138
}
1139
1140
=head2 open_hours_between
1141
1142
  $hours = $calendar->open_hours_between($start_date, $end_date)
1143
1144
Returns the number of hours between C<$start_date> and C<$end_date>, taking into
1145
account the opening hours of the library.
1146
1147
=cut
1148
1149
sub open_hours_between {
1150
    my ($self, $start_date, $end_date) = @_;
1151
    my $branchcode = $self->{branchcode};
1152
    my $schema = Koha::Database->new->schema;
1153
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1154
    $start_date = $dtf->format_datetime($start_date);
1155
    $end_date = $dtf->format_datetime($end_date);
1156
1157
    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
1158
        {
1159
            branchcode => $branchcode,
1160
            is_opened  => 1,
1161
        },
1162
        {
1163
            select => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'],
1164
            as     => [qw /hours_between/],
1165
            where  => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1166
        }
1167
    );
1168
1169
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1170
        {
1171
            branchcode => $branchcode,
1172
        },
1173
        {
1174
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1175
            rows => 1,
1176
        }
1177
    );
1178
1179
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1180
        {
1181
            branchcode => $branchcode,
1182
        },
1183
        {
1184
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1185
            rows => 1,
1186
        }
1187
    );
1188
1189
    #Capture the time portion of the date
1190
    $start_date =~ /\s(.*)/;
1191
    my $loan_date_time = $1;
1192
    $end_date =~ /\s(.*)/;
1193
    my $return_date_time = $1;
1194
1195
    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
1196
        {
1197
            branchcode => $branchcode,
1198
            is_opened  => 1,
1199
        },
1200
        {
1201
            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()],
1202
            as     => [qw /not_used_hours/],
1203
        }
1204
    );
1205
1206
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1207
}
1208
1209
=head2 addDuration
1210
1211
  my $dt = $calendar->addDuration($date, $dur, $unit)
1212
1213
C<$date> is a DateTime object representing the starting date of the interval.
1214
C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy)
1215
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
1216
1217
=cut
1218
1219
sub addDuration {
1220
    my ( $self, $startdate, $add_duration, $unit ) = @_;
1221
1222
    # Default to days duration (legacy support I guess)
1223
    if ( ref $add_duration ne 'DateTime::Duration' ) {
1224
        $add_duration = DateTime::Duration->new( days => $add_duration );
1225
    }
1226
1227
    $unit ||= 'days'; # default days ?
1228
    my $dt;
1229
1230
    if ( $unit eq 'hours' ) {
1231
        # Fixed for legacy support. Should be set as a branch parameter
1232
        my $return_by_hour = 10;
1233
1234
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1235
    } else {
1236
        # days
1237
        $dt = $self->addDays($startdate, $add_duration);
1238
    }
1239
1240
    return $dt;
1241
}
1242
1243
=head2 addHours
1244
1245
  $end = $calendar->addHours($start, $hours_duration, $return_by_hour)
1246
1247
Add C<$hours_duration> to C<$start> date.
1248
C<$return_by_hour> is an integer value representing the opening hour for the branch
1249
1250
=cut
1251
1252
sub addHours {
1253
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
1254
    my $base_date = $startdate->clone();
1255
1256
    $base_date->add_duration($hours_duration);
1257
1258
    # If we are using the calendar behave for now as if Datedue
1259
    # was the chosen option (current intended behaviour)
1260
1261
    if ( $self->{days_mode} ne 'Days' &&
1262
    $self->is_holiday($base_date) ) {
1263
1264
        if ( $hours_duration->is_negative() ) {
1265
            $base_date = $self->prev_open_day($base_date);
1266
        } else {
1267
            $base_date = $self->next_open_day($base_date);
1268
        }
1269
1270
        $base_date->set_hour($return_by_hour);
1271
1272
    }
1273
1274
    return $base_date;
1275
}
1276
1277
=head2 addDays
1278
1279
  $date = $calendar->addDays($start, $duration)
1280
1281
Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set
1282
to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue'
1283
it calculates the date normally, and then pushes to result to the next open day.
1284
1285
=cut
1286
1287
sub addDays {
1288
    my ( $self, $startdate, $days_duration ) = @_;
1289
    my $base_date = $startdate->clone();
1290
1291
    $self->{days_mode} ||= q{};
1292
1293
    if ( $self->{days_mode} eq 'Calendar' ) {
1294
        # use the calendar to skip all days the library is closed
1295
        # when adding
1296
        my $days = abs $days_duration->in_units('days');
1297
1298
        if ( $days_duration->is_negative() ) {
1299
            while ($days) {
1300
                $base_date = $self->prev_open_day($base_date);
1301
                --$days;
1302
            }
1303
        } else {
1304
            while ($days) {
1305
                $base_date = $self->next_open_day($base_date);
1306
                --$days;
1307
            }
1308
        }
1309
1310
    } else { # Days or Datedue
1311
        # use straight days, then use calendar to push
1312
        # the date to the next open day if Datedue
1313
        $base_date->add_duration($days_duration);
1314
1315
        if ( $self->{days_mode} eq 'Datedue' ) {
1316
            # Datedue, then use the calendar to push
1317
            # the date to the next open day if holiday
1318
            if (!$self->is_opened($base_date) ) {
1319
1320
                if ( $days_duration->is_negative() ) {
1321
                    $base_date = $self->prev_open_day($base_date);
1322
                } else {
1323
                    $base_date = $self->next_open_day($base_date);
1324
                }
1325
            }
1326
        }
1327
    }
1328
1329
    return $base_date;
1330
}
1331
1332
1;
(-)a/Koha/Hold.pm (-3 / +3 lines)
Lines 35-41 use Koha::Biblios; 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;
38
use Koha::DiscreteCalendar;
39
39
40
use Koha::Exceptions::Hold;
40
use Koha::Exceptions::Hold;
41
41
Lines 67-73 sub age { Link Here
67
    my $age;
67
    my $age;
68
68
69
    if ( $use_calendar ) {
69
    if ( $use_calendar ) {
70
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
70
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
71
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
71
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
72
    }
72
    }
73
    else {
73
    else {
Lines 207-213 sub set_waiting { Link Here
207
                branchcode   => $self->branchcode,
207
                branchcode   => $self->branchcode,
208
            }
208
            }
209
        );
209
        );
210
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
210
        my $calendar = Koha::DiscreteCalendar->new( branchcode => $self->branchcode, days_mode => $daysmode );
211
211
212
        $expirationdate = $calendar->days_forward( dt_from_string(), $max_pickup_delay );
212
        $expirationdate = $calendar->days_forward( dt_from_string(), $max_pickup_delay );
213
    }
213
    }
(-)a/circ/returns.pl (-1 / +2 lines)
Lines 50-58 use C4::Reserves; Link Here
50
use C4::RotatingCollections;
50
use C4::RotatingCollections;
51
use Koha::AuthorisedValues;
51
use Koha::AuthorisedValues;
52
use Koha::BiblioFrameworks;
52
use Koha::BiblioFrameworks;
53
use Koha::Calendar;
54
use Koha::Checkouts;
53
use Koha::Checkouts;
55
use Koha::DateUtils;
54
use Koha::DateUtils;
55
use Koha::DiscreteCalendar;
56
use Koha::Holds;
56
use Koha::Holds;
57
use Koha::Items;
57
use Koha::Items;
58
use Koha::Patrons;
58
use Koha::Patrons;
Lines 191-196 my $dropboxmode = $query->param('dropboxmode'); Link Here
191
my $dotransfer  = $query->param('dotransfer');
191
my $dotransfer  = $query->param('dotransfer');
192
my $canceltransfer = $query->param('canceltransfer');
192
my $canceltransfer = $query->param('canceltransfer');
193
my $dest = $query->param('dest');
193
my $dest = $query->param('dest');
194
my $calendar    = Koha::DiscreteCalendar->new({ branchcode => $userenv_branch });
194
#dropbox: get last open day (today - 1)
195
#dropbox: get last open day (today - 1)
195
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
196
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
196
197
(-)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 107-113 Link Here
107
<h5>Additional tools</h5>
107
<h5>Additional tools</h5>
108
<ul>
108
<ul>
109
    [% IF ( CAN_user_tools_edit_calendar ) %]
109
    [% IF ( CAN_user_tools_edit_calendar ) %]
110
	<li><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></li>
110
    <li><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></li>
111
    [% END %]
111
    [% END %]
112
    [% IF ( CAN_user_tools_manage_csv_profiles ) %]
112
    [% IF ( CAN_user_tools_manage_csv_profiles ) %]
113
	<li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
113
	<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>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) | html %] calendar</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 (-590 lines)
Lines 1-590 Link Here
1
[% USE raw %]
2
[% USE Asset %]
3
[% USE Branches %]
4
[% SET footerjs = 1 %]
5
[% INCLUDE 'doc-head-open.inc' %]
6
<title>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) | html %] calendar</title>
7
[% INCLUDE 'doc-head-close.inc' %]
8
<style> .key { padding : 3px; white-space:nowrap; line-height:230%; }
9
.ui-datepicker { font-size : 150%; }
10
.ui-datepicker th, .ui-datepicker .ui-datepicker-title select { font-size : 80%; }
11
.ui-datepicker td a { padding : .5em; }
12
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { font-size : 80%; }
13
.key { padding : 3px; white-space:nowrap; line-height:230%; }
14
.normalday { background-color :  #EDEDED; color :  Black; border : 1px solid #BCBCBC; }
15
.exception { background-color :  #b3d4ff; color :  Black; border : 1px solid #BCBCBC; }
16
.holiday {  background-color :  #ffaeae; color :  Black;  border : 1px solid #BCBCBC; }
17
.repeatableweekly {  background-color :  #FFFF99; color :  Black;  border : 1px solid #BCBCBC; }
18
.repeatableyearly {  background-color :  #FFCC66; color :  Black;  border : 1px solid #BCBCBC; }
19
td.exception a.ui-state-default { background:  #b3d4ff none; color :  Black; border : 1px solid #BCBCBC; }
20
td.holiday a.ui-state-default {  background:  #ffaeae none; color :  Black;  border : 1px solid #BCBCBC; }
21
td.repeatableweekly a.ui-state-default {  background:  #FFFF99 none; color :  Black;  border : 1px solid #BCBCBC; }
22
td.repeatableyearly a.ui-state-default {  background:  #FFCC66 none; color :  Black;  border : 1px solid #BCBCBC; }
23
.information { z-index : 1; background-color :  #DCD2F1; width : 300px; display : none; border : 1px solid #000000; color :  #000000; font-size :  8pt; font-weight :  bold; background-color :  #FFD700; cursor :  pointer; padding : 2px; }
24
.panel { z-index : 1; display : none; border : 3px solid #CCC; padding : 3px; margin-top: .3em;  background-color: #FEFEFE; } fieldset.brief { border : 0; margin-top: 0; }
25
#showHoliday { margin : .5em 0; } h1 select { width: 20em; } fieldset.brief ol { font-size:100%; } fieldset.brief li, fieldset.brief li.radio  { padding:0.2em 0; } .help { margin:.3em 0;border:1px solid #EEE;padding:.3em .7em; font-size : 90%; } #holidayweeklyrepeatable, #holidaysyearlyrepeatable, #holidaysunique, #holidayexceptions { font-size : 90%; margin-bottom : 1em;} .calendar td, .calendar th, .calendar .button, .calendar tbody .day { padding : .7em; font-size: 110%; } .calendar { width: auto; border : 0; }
26
</style>
27
</head>
28
<body id="tools_holidays" class="tools">
29
[% INCLUDE 'header.inc' %]
30
[% INCLUDE 'cat-search.inc' %]
31
32
<nav id="breadcrumbs" aria-label="Breadcrumb" class="breadcrumb">
33
    <ol>
34
        <li>
35
            <a href="/cgi-bin/koha/mainpage.pl">Home</a>
36
        </li>
37
        <li>
38
            <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a>
39
        </li>
40
        <li>
41
            <a href="#" aria-current="page">
42
                [% Branches.GetName( branch ) | html %] calendar
43
            </a>
44
        </li>
45
    </ol>
46
</nav>
47
48
<div class="main container-fluid">
49
    <div class="row">
50
        <div class="col-sm-10 col-sm-push-2">
51
            <main>
52
53
    <h2>[% Branches.GetName( branch ) | html %] calendar</h2>
54
55
    <div class="row">
56
    <div class="col-sm-6">
57
        <label for="branch">Define the holidays for:</label>
58
            <select id="branch" name="branch">
59
                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
60
            </select>
61
    
62
    <!-- ******************************** FLAT PANELS ******************************************* -->
63
    <!-- *****           Makes all the flat panel to deal with holidays                     ***** -->
64
    <!-- **************************************************************************************** -->
65
66
    <!-- ********************** Panel for showing already loaded holidays *********************** -->
67
    <div class="panel" id="showHoliday">
68
         <form action="/cgi-bin/koha/tools/exceptionHolidays.pl" method="post">
69
             <input type="hidden" id="showHolidayType" name="showHolidayType" value="" />
70
            <fieldset class="brief">
71
            <h3>Edit this holiday</h3>
72
            <span id="holtype"></span>
73
            <ol>
74
            <li>
75
                <strong>Library:</strong> <span id="showBranchNameOutput"></span>
76
                <input type="hidden" id="showBranchName" name="showBranchName" />
77
            </li>
78
            <li>
79
                <strong>From date:</strong>
80
                <span id="showDaynameOutput"></span>, 
81
                
82
                                [% IF ( dateformat == "us" ) %]<span id="showMonthOutput"></span>/<span id="showDayOutput"></span>/<span id="showYearOutput"></span>[% ELSIF ( dateformat == "metric") %]<span id="showDayOutput"></span>/<span id="showMonthOutput"></span>/<span id="showYearOutput"></span>[% ELSIF ( dateformat == "dmydot") %]<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>[% END %]
83
84
                <input type="hidden" id="showDayname" name="showDayname" />
85
                <input type="hidden" id="showWeekday" name="showWeekday" />
86
                <input type="hidden" id="showDay" name="showDay" />
87
                <input type="hidden" id="showMonth" name="showMonth" />
88
                <input type="hidden" id="showYear" name="showYear" />
89
            </li>
90
            <li class="dateinsert">
91
                <strong>To date: </strong>
92
                <input type="text" id="datecancelrange" name="datecancelrange" size="20" value="[% datecancelrange | html %]" class="datepicker"/>
93
            </li>
94
            <li><label for="showTitle">Title: </label><input type="text" name="showTitle" id="showTitle" size="35" /></li>
95
            <!-- showTitle is necessary for exception radio button to work properly -->
96
            <li>
97
                <label for="showDescription">Description:</label>
98
                <textarea rows="2" cols="40" id="showDescription" name="showDescription"></textarea>
99
            </li>
100
            <li class="radio"><div class="exceptionPossibility" style="position:static">
101
                <input type="radio" name="showOperation" id="showOperationExc" value="exception" /> <label for="showOperationExc">Generate an exception for this repeated holiday.</label>
102
                <a href="#" class="helptext">[?]</a>
103
                <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>
104
            </div></li>
105
            <li class="radio"><div class="exceptionPossibility" style="position:static">
106
                <input type="radio" name="showOperation" id="showOperationExcRange" value="exceptionrange" />
107
                <label for="showOperationExcRange">Generate exceptions on a range of dates.</label>
108
                <a href="#" class="helptext">[?]</a>
109
                <div class="hint">You can make an exception on a range of dates repeated yearly.</div>
110
            </div></li>
111
            <li class="radio"><input type="radio" name="showOperation" id="showOperationDel" value="delete" /> <label for="showOperationDel" id="showOperationDelLabel">Delete this holiday</label>
112
                <a href="#" class="helptext">[?]</a>
113
                <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></li>
114
            <li class="radio"><input type="radio" name="showOperation" id="showOperationDelRange" value="deleterange" /> <label for="showOperationDelRange" id="showOperationDelLabelRange">Delete the single holidays on a range</label>.
115
                <a href="#" class="helptext">[?]</a>
116
                <div class="hint">This will delete the single holidays rules only. The repeatable holidays and exceptions will not be deleted.</div>
117
            </li>
118
            <li class="radio"><input type="radio" name="showOperation" id="showOperationDelRangeRepeat" value="deleterangerepeat" /> <label for="showOperationDelRangeRepeat" id="showOperationDelLabelRangeRepeat">Delete the repeated holidays on a range</label>.
119
                <a href="#" class="helptext">[?]</a>
120
                <div class="hint">This will delete the repeated holidays rules only. The repeatable holidays will be deleted but not the exceptions.</div>
121
            </li>
122
            <li class="radio"><input type="radio" name="showOperation" id="showOperationDelRangeRepeatExcept" value="deleterangerepeatexcept" /> <label for="showOperationDelRangeRepeatExcept" id="showOperationDelLabelRangeRepeatExcept">Delete the exceptions on a range</label>.
123
                <a href="#" class="helptext">[?]</a>
124
                <div class="hint">This will delete the exceptions inside a given range. Be careful about your scope range; if it is oversized you could slow down Koha.</div>
125
            </li>
126
            <li class="radio"><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" value="Save" />
138
                <a href="#" class="cancel hidePanel showHoliday">Cancel</a>
139
            </fieldset>
140
            </fieldset>
141
        </form>
142
    </div>
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" ) %]<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "metric" ) %]<span id="newDayOutput"></span>/<span id="newMonthOutput"></span>/<span id="newYearOutput"></span>[% ELSIF ( dateformat == "dmydot" ) %]<span id="newDayOutput"></span>.<span id="newMonthOutput"></span>.<span id="newYearOutput"></span>[% ELSE %]<span id="newYearOutput"></span>/<span id="newMonthOutput"></span>/<span id="newDayOutput"></span>[% END %]
160
161
                <input type="hidden" id="newDayname" name="showDayname" />
162
                <input type="hidden" id="newWeekday" name="newWeekday" />
163
                <input type="hidden" id="newDay" name="newDay" />
164
                <input type="hidden" id="newMonth" name="newMonth" />
165
                <input type="hidden" id="newYear" name="newYear" />
166
            </li>
167
            <li class="dateinsert">
168
                <strong>To date: </strong>
169
                <input type="text" id="dateofrange" name="dateofrange" size="20" value="[% dateofrange | html %]" class="datepicker" />
170
            </li>
171
            <li><label for="title">Title: </label><input type="text" name="newTitle" id="title" size="35" /></li>
172
            <li><label for="newDescription">Description:</label>
173
                <textarea rows="2" cols="40" id="newDescription" name="newDescription"></textarea>
174
            </li>
175
            <li class="radio"><input type="radio" name="newOperation" id="newOperationOnce" value="holiday" checked="checked" />
176
            <label for="newOperationOnce">Holiday only on this day</label>.
177
            <a href="#" class="helptext">[?]</a>
178
            <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>
179
            </li>
180
            <li class="radio"><input type="radio" name="newOperation" id="newOperationDay" value="weekday" />
181
                            <label for="newOperationDay">Holiday repeated every same day of the week</label>.
182
                            <a href="#" class="helptext">[?]</a>
183
                            <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>
184
                            </li>
185
            <li class="radio"><input type="radio" name="newOperation" id="newOperationYear" value="repeatable" />
186
                            <label for="newOperationYear">Holiday repeated yearly on the same date</label>.
187
                            <a href="#" class="helptext">[?]</a>
188
                            <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>
189
                            </li>
190
            <li class="radio"><input type="radio" name="newOperation" id="newOperationField" value="holidayrange" />
191
                            <label for="newOperationField">Holidays on a range</label>.
192
                            <a href="#" class="helptext">[?]</a>
193
                            <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>
194
                            </li>
195
            <li class="radio"><input type="radio" name="newOperation" id="newOperationFieldyear" value="holidayrangerepeat" />
196
                            <label for="newOperationFieldyear">Holidays repeated yearly on a range</label>.
197
                            <a href="#" class="helptext">[?]</a>
198
                            <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>
199
                            </li>
200
                <li class="checkbox">
201
                <input type="checkbox" name="allBranches" id="allBranches" />
202
                <label for="allBranches">Copy to all libraries</label>.
203
                <a href="#" class="helptext">[?]</a>
204
                <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>
205
                </li></ol>
206
                <fieldset class="action">
207
                    <input type="submit" name="submit" value="Save" />
208
                    <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
209
                </fieldset>
210
                </fieldset>
211
         </form>
212
    </div>
213
214
    <!-- *************************************************************************************** -->
215
    <!-- ******                          END OF FLAT PANELS                               ****** -->
216
    <!-- *************************************************************************************** -->
217
218
<!-- ************************************************************************************** -->
219
<!-- ******                              MAIN SCREEN CODE                            ****** -->
220
<!-- ************************************************************************************** -->
221
<h3>Calendar information</h3>
222
<div id="jcalendar-container"></div>
223
224
<div style="margin-top: 2em;">
225
<form action="copy-holidays.pl" method="post">
226
    <input type="hidden" name="from_branchcode" value="[% branch | html %]" />
227
  <label for="branchcode">Copy holidays to:</label>
228
  <select id="branchcode" name="branchcode">
229
    <option value=""></option>
230
    [% FOREACH l IN Branches.all() %]
231
    <option value="[% l.branchcode | html %]">[% l.branchname | html %]</option>
232
    [% END %]
233
  </select>
234
    <input type="submit" value="Copy" />
235
</form>
236
</div>
237
238
</div>
239
<div class="col-sm-6">
240
<div class="help">
241
<h4>Hints</h4>
242
    <ul>
243
        <li>Search in the calendar the day you want to set as holiday.</li>
244
        <li>Click the date to add or edit a holiday.</li>
245
        <li>Enter a title and description for the holiday.</li>
246
        <li>Specify how the holiday should repeat.</li>
247
        <li>Click Save to finish.</li>
248
    </ul>
249
<h4>Key</h4>
250
    <p>
251
        <span class="key normalday">Working day</span>
252
        <span class="key holiday">Unique holiday</span>
253
        <span class="key repeatableweekly">Holiday repeating weekly</span>
254
        <span class="key repeatableyearly">Holiday repeating yearly</span>
255
        <span class="key exception">Holiday exception</span>
256
    </p>
257
</div>
258
<div id="holiday-list">
259
<!-- Exceptions First -->
260
<!--   this will probably always have the least amount of data -->
261
[% IF ( EXCEPTION_HOLIDAYS_LOOP ) %]
262
<h3>Exceptions</h3>
263
  <table id="holidayexceptions">
264
<thead><tr>
265
  <th class="exception">Date</th>
266
  <th class="exception">Title</th>
267
  <th class="exception">Description</th>
268
</tr>
269
</thead>
270
<tbody>
271
  [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
272
  <tr>
273
  <td><a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% EXCEPTION_HOLIDAYS_LOO.DATE | uri %]"><span title="[% EXCEPTION_HOLIDAYS_LOO.DATE_SORT | html %]">[% EXCEPTION_HOLIDAYS_LOO.DATE | html %]</span></a></td>
274
  <td>[% EXCEPTION_HOLIDAYS_LOO.TITLE | html %]</td>
275
  <td>[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
276
  </tr>
277
  [% END %] 
278
</tbody>
279
</table>
280
[% END %]
281
282
[% IF ( WEEK_DAYS_LOOP ) %]
283
<h3>Weekly - Repeatable holidays</h3>
284
<table id="holidayweeklyrepeatable">
285
<thead>
286
<tr>
287
  <th class="repeatableweekly">Day of week</th>
288
  <th class="repeatableweekly">Title</th>
289
  <th class="repeatableweekly">Description</th>
290
</tr>
291
</thead>
292
<tbody>
293
  [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
294
  <tr>
295
  <td>[% WEEK_DAYS_LOO.KEY | html %]</td>
296
  </td> 
297
  <td>[% WEEK_DAYS_LOO.TITLE | html %]</td>
298
  <td>[% WEEK_DAYS_LOO.DESCRIPTION | html %]</td>
299
  </tr>
300
  [% END %] 
301
</tbody>
302
</table>
303
[% END %]
304
305
[% IF ( DAY_MONTH_HOLIDAYS_LOOP ) %]
306
<h3>Yearly - Repeatable holidays</h3>
307
<table id="holidaysyearlyrepeatable">
308
<thead>
309
<tr>
310
  [% IF ( dateformat == "metric" ) %]
311
  <th class="repeatableyearly">Day/month</th>
312
  [% ELSE %]
313
  <th class="repeatableyearly">Month/day</th>
314
  [% END %]
315
  <th class="repeatableyearly">Title</th>
316
  <th class="repeatableyearly">Description</th>
317
</tr>
318
</thead>
319
<tbody>
320
  [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
321
  <tr>
322
  <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT | html %]">[% DAY_MONTH_HOLIDAYS_LOO.DATE | html %]</span></td>
323
  <td>[% DAY_MONTH_HOLIDAYS_LOO.TITLE | html %]</td>
324
  <td>[% DAY_MONTH_HOLIDAYS_LOO.DESCRIPTION | html %]</td>
325
  </tr>
326
  [% END %] 
327
</tbody>
328
</table>
329
[% END %]
330
331
[% IF ( HOLIDAYS_LOOP ) %]
332
<h3>Unique holidays</h3>
333
<table id="holidaysunique">
334
<thead>
335
<tr>
336
  <th class="holiday">Date</th>
337
  <th class="holiday">Title</th>
338
  <th class="holiday">Description</th>
339
</tr>
340
</thead>
341
<tbody>
342
    [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
343
<tr>
344
  <td><a href="/cgi-bin/koha/tools/holidays.pl?branch=[% branch | uri %]&amp;calendardate=[% HOLIDAYS_LOO.DATE | uri %]"><span title="[% HOLIDAYS_LOO.DATE_SORT | html %]">[% HOLIDAYS_LOO.DATE | html %]</span></a></td>
345
  <td>[% HOLIDAYS_LOO.TITLE | html %]</td>
346
  <td>[% HOLIDAYS_LOO.DESCRIPTION.replace('\\\r\\\n', '<br />') | html %]</td>
347
</tr>
348
  [% END %] 
349
</tbody>
350
</table>
351
[% END %]
352
</div>
353
</div>
354
</div>
355
356
            </main>
357
        </div> <!-- /.col-sm-10.col-sm-push-2 -->
358
359
        <div class="col-sm-2 col-sm-pull-10">
360
            <aside>
361
                [% INCLUDE 'tools-menu.inc' %]
362
            </aside>
363
        </div> <!-- .col-sm-2.col-sm-pull-10 -->
364
     </div> <!-- /.row -->
365
366
[% MACRO jsinclude BLOCK %]
367
    [% INCLUDE 'calendar.inc' %]
368
    [% INCLUDE 'datatables.inc' %]
369
    [% Asset.js("js/tools-menu.js") | $raw %]
370
    <script>
371
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
372
373
        /* Creates all the structures to deal with all different kinds of holidays */
374
        var week_days = new Array();
375
        var holidays = new Array();
376
        var holidates = new Array();
377
        var exception_holidays = new Array();
378
        var day_month_holidays = new Array();
379
        var hola= "[% code | html %]";
380
        [% FOREACH WEEK_DAYS_LOO IN WEEK_DAYS_LOOP %]
381
        week_days["[% WEEK_DAYS_LOO.KEY | html %]"] = {title:"[% WEEK_DAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% WEEK_DAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
382
        [% END %]
383
        [% FOREACH HOLIDAYS_LOO IN HOLIDAYS_LOOP %]
384
        holidates.push("[% HOLIDAYS_LOO.KEY | html %]");
385
        holidays["[% HOLIDAYS_LOO.KEY | html %]"] = {title:"[% HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
386
        [% END %]
387
        [% FOREACH EXCEPTION_HOLIDAYS_LOO IN EXCEPTION_HOLIDAYS_LOOP %]
388
        exception_holidays["[% EXCEPTION_HOLIDAYS_LOO.KEY | html %]"] = {title:"[% EXCEPTION_HOLIDAYS_LOO.TITLE | replace('"','\"') | html %]", description:"[% EXCEPTION_HOLIDAYS_LOO.DESCRIPTION | replace('"','\"') | html %]"};
389
        [% END %]
390
        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN DAY_MONTH_HOLIDAYS_LOOP %]
391
        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 %]"};
392
        [% END %]
393
394
        function holidayOperation(formObject, opType) {
395
            var op = document.getElementsByName('operation');
396
            op[0].value = opType;
397
            formObject.submit();
398
        }
399
400
        // This function shows the "Show Holiday" panel //
401
        function showHoliday (exceptionPossibility, dayName, day, month, year, weekDay, title, description, holidayType) {
402
            $("#newHoliday").slideUp("fast");
403
            $("#showHoliday").slideDown("fast");
404
            $('#showDaynameOutput').html(dayName);
405
            $('#showDayname').val(dayName);
406
            $('#showBranchNameOutput').html($("#branch :selected").text());
407
            $('#showBranchName').val($("#branch").val());
408
            $('#showDayOutput').html(day);
409
            $('#showDay').val(day);
410
            $('#showMonthOutput').html(month);
411
            $('#showMonth').val(month);
412
            $('#showYearOutput').html(year);
413
            $('#showYear').val(year);
414
            $('#showDescription').val(description);
415
            $('#showWeekday:first').val(weekDay);
416
            $('#showTitle').val(title);
417
            $('#showHolidayType').val(holidayType);
418
419
            if (holidayType == 'exception') {
420
                $("#showOperationDelLabel").html(_("Delete this exception."));
421
                $("#holtype").attr("class","key exception").html(_("Holiday exception"));
422
            } else if(holidayType == 'weekday') {
423
                $("#showOperationDelLabel").html(_("Delete this holiday."));
424
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
425
            } else if(holidayType == 'daymonth') {
426
                $("#showOperationDelLabel").html(_("Delete this holiday."));
427
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
428
            } else {
429
                $("#showOperationDelLabel").html(_("Delete this holiday."));
430
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
431
            }
432
433
            if (exceptionPossibility == 1) {
434
                $(".exceptionPossibility").parent().show();
435
            } else {
436
                $(".exceptionPossibility").parent().hide();
437
            }
438
        }
439
440
        // This function shows the "Add Holiday" panel //
441
        function newHoliday (dayName, day, month, year, weekDay) {
442
            $("#showHoliday").slideUp("fast");
443
            $("#newHoliday").slideDown("fast");
444
            $("#newDaynameOutput").html(dayName);
445
            $("#newDayname").val(dayName);
446
            $("#newBranchNameOutput").html($('#branch :selected').text());
447
            $("#newBranchName").val($('#branch').val());
448
            $("#newDayOutput").html(day);
449
            $("#newDay").val(day);
450
            $("#newMonthOutput").html(month);
451
            $("#newMonth").val(month);
452
            $("#newYearOutput").html(year);
453
            $("#newYear").val(year);
454
            $("#newWeekday:first").val(weekDay);
455
        }
456
457
        function hidePanel(aPanelName) {
458
            $("#"+aPanelName).slideUp("fast");
459
        }
460
461
        function changeBranch () {
462
            var branch = $("#branch option:selected").val();
463
            location.href='/cgi-bin/koha/tools/holidays.pl?branch=' + branch + '&calendardate=' + "[% calendardate | html %]";
464
        }
465
466
        function Help() {
467
            newin=window.open("/cgi-bin/koha/help.pl","KohaHelp",'width=600,height=600,toolbar=false,scrollbars=yes');
468
        }
469
470
        /* This function gives css clases to each kind of day */
471
        function dateStatusHandler(date) {
472
            date = new Date(date);
473
            var day = date.getDate();
474
            var month = date.getMonth() + 1;
475
            var year = date.getFullYear();
476
            var weekDay = date.getDay();
477
            var dayMonth = month + '/' + day;
478
            var dateString = year + '/' + month + '/' + day;
479
            if (exception_holidays[dateString] != null) {
480
                return [true, "exception", _("Exception: %s").format(exception_holidays[dateString].title)];
481
            } else if ( week_days[weekDay] != null ){
482
                return [true, "repeatableweekly", _("Weekly holiday: %s").format(week_days[weekDay].title)];
483
            } else if ( day_month_holidays[dayMonth] != null ) {
484
                return [true, "repeatableyearly", _("Yearly holiday: %s").format(day_month_holidays[dayMonth].title)];
485
            } else if (holidays[dateString] != null) {
486
                return [true, "holiday", _("Single holiday: %s").format(holidays[dateString].title)];
487
            } else {
488
                return [true, "normalday", _("Normal day")];
489
            }
490
        }
491
492
        /* This function is in charge of showing the correct panel considering the kind of holiday */
493
        function dateChanged(calendar) {
494
            calendar = new Date(calendar);
495
            var day = calendar.getDate();
496
            var month = calendar.getMonth() + 1;
497
            var year = calendar.getFullYear();
498
            var weekDay = calendar.getDay();
499
            var dayName = weekdays[weekDay];
500
            var dayMonth = month + '/' + day;
501
            var dateString = year + '/' + month + '/' + day;
502
                if (holidays[dateString] != null) {
503
                    showHoliday(0, dayName, day, month, year, weekDay, holidays[dateString].title,     holidays[dateString].description, 'ymd');
504
                } else if (exception_holidays[dateString] != null) {
505
                    showHoliday(0, dayName, day, month, year, weekDay, exception_holidays[dateString].title, exception_holidays[dateString].description, 'exception');
506
                } else if (week_days[weekDay] != null) {
507
                    showHoliday(1, dayName, day, month, year, weekDay, week_days[weekDay].title,     week_days[weekDay].description, 'weekday');
508
                } else if (day_month_holidays[dayMonth] != null) {
509
                    showHoliday(1, dayName, day, month, year, weekDay, day_month_holidays[dayMonth].title, day_month_holidays[dayMonth].description, 'daymonth');
510
                } else {
511
                    newHoliday(dayName, day, month, year, weekDay);
512
                }
513
        };
514
515
        $(document).ready(function() {
516
517
            $(".hint").hide();
518
            $("#branch").change(function(){
519
                changeBranch();
520
            });
521
            $("#holidayweeklyrepeatable>tbody>tr").each(function(){
522
                var first_td = $(this).find('td').first();
523
                first_td.html(weekdays[first_td.html()]);
524
            });
525
            $("#holidayweeklyrepeatable").dataTable($.extend(true, {}, dataTablesDefaults, {
526
                "sDom": 't',
527
                "bPaginate": false
528
            }));
529
            $("#holidayexceptions,#holidaysyearlyrepeatable,#holidaysunique").dataTable($.extend(true, {}, dataTablesDefaults, {
530
                "sDom": 't',
531
                "aoColumns": [
532
                    { "sType": "title-string" },null,null
533
                ],
534
                "bPaginate": false
535
            }));
536
            $("a.helptext").click(function(){
537
                $(this).parent().find(".hint").toggle(); return false;
538
            });
539
            $("#dateofrange").datepicker({
540
                beforeShow: function() {
541
                    var startdate = $("#jcalendar-container").datepicker("getDate");
542
                    if (startdate !== null) {
543
                        var sd = new Date(startdate);
544
                        var ed = new Date($(this).datepicker("getDate"));
545
                        if (ed < sd) {
546
                            $(this).datepicker("setDate", startdate);
547
                            $(this).datepicker("option", "defaultDate", startdate);
548
                        }
549
                    }
550
                }
551
            });
552
            $("#datecancelrange").datepicker();
553
            $("#dateofrange").each(function () { this.value = "" });
554
            $("#datecancelrange").each(function () { this.value = "" });
555
            $("#jcalendar-container").datepicker({
556
                beforeShowDay: function(thedate) {
557
                    var day = thedate.getDate();
558
                    var month = thedate.getMonth() + 1;
559
                    var year = thedate.getFullYear();
560
                    var dateString = year + '/' + month + '/' + day;
561
                    return dateStatusHandler(dateString);
562
                },
563
                onSelect: function(dateText, inst) {
564
                    dateChanged($(this).datepicker("getDate"));
565
                    var enddate = $("#dateofrange").datepicker("getDate");
566
                    $("#dateofrange").datepicker("option", "defaultDate", $(this).datepicker("getDate"));
567
                    $("#dateofrange").datepicker( "option", "minDate", $(this).datepicker("getDate")); //ensure end date can't be before start date
568
                    if (enddate !== null) {
569
                        var ed = new Date(enddate);
570
                        var sd = new Date($(this).datepicker("getDate"));
571
                        if (ed < sd) {
572
                            $("#dateofrange").datepicker("setDate", $(this).datepicker("getDate"));
573
                            $("#dateofrange").datepicker("option", "defaultDate", enddate);
574
                        }
575
                    }
576
                },
577
                defaultDate: new Date("[% keydate | html %]")
578
            });
579
            $(".hidePanel").on("click",function(){
580
                if( $(this).hasClass("showHoliday") ){
581
                    hidePanel("showHoliday");
582
                } else {
583
                    hidePanel("newHoliday");
584
                }
585
            })
586
        });
587
    </script>
588
[% END %]
589
590
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +1 lines)
Lines 102-108 Link Here
102
[% END %]
102
[% END %]
103
<dl>
103
<dl>
104
    [% IF ( CAN_user_tools_edit_calendar ) %]
104
    [% IF ( CAN_user_tools_edit_calendar ) %]
105
    <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
105
    <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
106
    <dd>Define days when the library is closed</dd>
106
    <dd>Define days when the library is closed</dd>
107
    [% END %]
107
    [% END %]
108
108
(-)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;
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-44 use Carp; Link Here
38
use File::Spec;
38
use File::Spec;
39
use Try::Tiny;
39
use Try::Tiny;
40
40
41
use Koha::Calendar;
41
use Koha::DiscreteCalendar;
42
use Koha::DateUtils;
42
use Koha::DateUtils;
43
use Koha::Patrons;
43
use Koha::Patrons;
44
use C4::Log;
44
use C4::Log;
Lines 203-209 EOM Link Here
203
sub set_holiday {
203
sub set_holiday {
204
    my ( $branch, $dt ) = @_;
204
    my ( $branch, $dt ) = @_;
205
205
206
    my $calendar = Koha::Calendar->new( branchcode => $branch );
206
    my $calendar = Koha::DiscreteCalendar->new( branchcode => $branch );
207
    return $calendar->is_holiday($dt);
207
    return $calendar->is_holiday($dt);
208
}
208
}
209
209
(-)a/misc/cronjobs/holds/cancel_unfilled_holds.pl (-1 / +1 lines)
Lines 32-38 use Koha::Script -cron; Link Here
32
use C4::Reserves;
32
use C4::Reserves;
33
use C4::Log;
33
use C4::Log;
34
use Koha::Holds;
34
use Koha::Holds;
35
use Koha::Calendar;
35
use Koha::DiscreteCalendar;
36
use Koha::DateUtils;
36
use Koha::DateUtils;
37
use Koha::Libraries;
37
use Koha::Libraries;
38
38
(-)a/misc/cronjobs/overdue_notices.pl (-11 / +7 lines)
Lines 41-47 use C4::Overdues qw(GetFine GetOverdueMessageTransportTypes parse_overdues_lette Link Here
41
use C4::Log;
41
use C4::Log;
42
use Koha::Patron::Debarments qw(AddUniqueDebarment);
42
use Koha::Patron::Debarments qw(AddUniqueDebarment);
43
use Koha::DateUtils;
43
use Koha::DateUtils;
44
use Koha::Calendar;
44
use Koha::DiscreteCalendar;
45
use Koha::Libraries;
45
use Koha::Libraries;
46
use Koha::Acquisition::Currencies;
46
use Koha::Acquisition::Currencies;
47
use Koha::Patrons;
47
use Koha::Patrons;
Lines 460-468 elsif ( defined $text_filename ) { Link Here
460
}
460
}
461
461
462
foreach my $branchcode (@branches) {
462
foreach my $branchcode (@branches) {
463
    my $calendar;
464
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
463
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
465
        $calendar = Koha::Calendar->new( branchcode => $branchcode );
464
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
466
        if ( $calendar->is_holiday($date_to_run) ) {
465
        if ( $calendar->is_holiday($date_to_run) ) {
467
            next;
466
            next;
468
        }
467
        }
Lines 575-587 END_SQL Link Here
575
                my $days_between;
574
                my $days_between;
576
                if ( C4::Context->preference('OverdueNoticeCalendar') )
575
                if ( C4::Context->preference('OverdueNoticeCalendar') )
577
                {
576
                {
578
                    $days_between =
577
                    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
579
                      $calendar->days_between( dt_from_string($data->{date_due}),
578
                    $days_between = $calendar->days_between( dt_from_string($data->{date_due}), $date_to_run );
580
                        $date_to_run );
581
                }
579
                }
582
                else {
580
                else {
583
                    $days_between =
581
                    $days_between = $date_to_run->delta_days( dt_from_string($data->{date_due}) );
584
                      $date_to_run->delta_days( dt_from_string($data->{date_due}) );
585
                }
582
                }
586
                $days_between = $days_between->in_units('days');
583
                $days_between = $days_between->in_units('days');
587
                if ($triggered) {
584
                if ($triggered) {
Lines 658-666 END_SQL Link Here
658
                my $exceededPrintNoticesMaxLines = 0;
655
                my $exceededPrintNoticesMaxLines = 0;
659
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
656
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
660
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
657
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
661
                        $days_between =
658
                        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
662
                          $calendar->days_between(
659
                        $days_between = $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
663
                            dt_from_string( $item_info->{date_due} ), $date_to_run );
664
                    }
660
                    }
665
                    else {
661
                    else {
666
                        $days_between =
662
                        $days_between =
(-)a/misc/cronjobs/staticfines.pl (-2 / +2 lines)
Lines 41-47 use Koha::Script -cron; Link Here
41
use C4::Context;
41
use C4::Context;
42
use C4::Circulation;
42
use C4::Circulation;
43
use C4::Overdues;
43
use C4::Overdues;
44
use C4::Calendar qw();    # don't need any exports from Calendar
44
use Koha::DiscreteCalendar qw();    # don't need any exports from Calendar
45
use C4::Biblio;
45
use C4::Biblio;
46
use C4::Debug;            # supplying $debug and $cgi_debug
46
use C4::Debug;            # supplying $debug and $cgi_debug
47
use C4::Log;
47
use C4::Log;
Lines 177-183 for ( my $i = 0 ; $i < scalar(@$data) ; $i++ ) { Link Here
177
177
178
    my $calendar;
178
    my $calendar;
179
    unless ( defined( $calendars{$branchcode} ) ) {
179
    unless ( defined( $calendars{$branchcode} ) ) {
180
        $calendars{$branchcode} = C4::Calendar->new( branchcode => $branchcode );
180
        $calendars{$branchcode} = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
181
    }
181
    }
182
    $calendar = $calendars{$branchcode};
182
    $calendar = $calendars{$branchcode};
183
    my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
183
    my $isHoliday = $calendar->isHoliday( $tday, $tmonth, $tyear );
(-)a/misc/cronjobs/thirdparty/TalkingTech_itiva_outbound.pl (-2 / +2 lines)
Lines 36-42 use C4::Context; Link Here
36
use C4::Items;
36
use C4::Items;
37
use C4::Letters;
37
use C4::Letters;
38
use C4::Overdues;
38
use C4::Overdues;
39
use Koha::Calendar;
39
use Koha::DiscreteCalendar;
40
use Koha::DateUtils;
40
use Koha::DateUtils;
41
use Koha::Patrons;
41
use Koha::Patrons;
42
use Koha::Libraries;
42
use Koha::Libraries;
Lines 341-347 sub GetWaitingHolds { Link Here
341
            }
341
            }
342
        );
342
        );
343
343
344
        my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
344
        my $calendar = Koha::DiscreteCalendar->new( branchcode => $issue->{'site'}, days_mode => $daysmode );
345
345
346
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
346
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
347
        my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
347
        my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
(-)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;
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;
24
use C4::Output;
25
26
use Koha::DateUtils;
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;
8
use C4::Output;
9
use DateTime;
10
11
use C4::Calendar;
12
use Koha::DateUtils;
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;
24
use C4::Output;
25
26
use C4::Calendar;
27
use Koha::DateUtils;
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
                             debug => 1,
39
                           });
40
41
# calendardate - date passed in url for human readability (syspref)
42
# if the url has an invalid date default to 'now.'
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;
25
use C4::Output;
26
27
use C4::Calendar;
28
use DateTime;
29
use Koha::DateUtils;
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