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

(-)a/C4/Calendar.pm (-713 lines)
Lines 1-713 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
    $cache->clear_from_cache( 'single_holidays') ;
281
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
326
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
427
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
470
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
551
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
582
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $cache->clear_from_cache( 'single_holidays') ;
636
    $cache->clear_from_cache( 'exception_holidays') ;
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
    $target_calendar->insert_week_day_holiday( weekday => $_, %{ $wdh->{$_} } )
694
      foreach keys %$wdh;
695
    $target_calendar->insert_day_month_holiday(%$_)
696
      foreach values %{ $self->get_day_month_holidays };
697
    $target_calendar->insert_exception_holiday(%$_)
698
      foreach grep { $_->{date} gt $today } values %{ $self->get_exception_holidays };
699
    $target_calendar->insert_single_holiday(%$_)
700
      foreach grep { $_->{date} gt $today } values %{ $self->get_single_holidays };
701
702
    return 1;
703
}
704
705
1;
706
707
__END__
708
709
=head1 AUTHOR
710
711
Koha Physics Library UNLP <matias_veleda@hotmail.com>
712
713
=cut
(-)a/C4/Circulation.pm (-8 / +7 lines)
Lines 44-50 use Koha::Account; Link Here
44
use Koha::AuthorisedValues;
44
use Koha::AuthorisedValues;
45
use Koha::Biblioitems;
45
use Koha::Biblioitems;
46
use Koha::DateUtils;
46
use Koha::DateUtils;
47
use Koha::Calendar;
47
use Koha::DiscreteCalendar;
48
use Koha::Checkouts;
48
use Koha::Checkouts;
49
use Koha::IssuingRules;
49
use Koha::IssuingRules;
50
use Koha::Items;
50
use Koha::Items;
Lines 1232-1238 sub checkHighHolds { Link Here
1232
1232
1233
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1233
        my $issuedate = DateTime->now( time_zone => C4::Context->tz() );
1234
1234
1235
        my $calendar = Koha::Calendar->new( branchcode => $branch );
1235
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
1236
1236
1237
        my $itype = $item_object->effective_itemtype;
1237
        my $itype = $item_object->effective_itemtype;
1238
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
1238
        my $orig_due = C4::Circulation::CalcDateDue( $issuedate, $itype, $branch, $borrower );
Lines 2280-2289 sub _debar_user_on_return { Link Here
2280
            my $new_debar_dt;
2280
            my $new_debar_dt;
2281
            # Use the calendar or not to calculate the debarment date
2281
            # Use the calendar or not to calculate the debarment date
2282
            if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
2282
            if ( C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed' ) {
2283
                my $calendar = Koha::Calendar->new(
2283
                my $calendar = Koha::DiscreteCalendar->new( {
2284
                    branchcode => $branchcode,
2284
                    branchcode => $branchcode,
2285
                    days_mode  => 'Calendar'
2285
                    days_mode  => 'Calendar'
2286
                );
2286
                } );
2287
                $new_debar_dt = $calendar->addDate( $return_date, $suspension_days );
2287
                $new_debar_dt = $calendar->addDate( $return_date, $suspension_days );
2288
            }
2288
            }
2289
            else {
2289
            else {
Lines 3531-3537 sub CalcDateDue { Link Here
3531
        else { # days
3531
        else { # days
3532
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3532
            $dur = DateTime::Duration->new( days => $loanlength->{$length_key});
3533
        }
3533
        }
3534
        my $calendar = Koha::Calendar->new( branchcode => $branch );
3534
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3535
        $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
3535
        $datedue = $calendar->addDate( $datedue, $dur, $loanlength->{lengthunit} );
3536
        if ($loanlength->{lengthunit} eq 'days') {
3536
        if ($loanlength->{lengthunit} eq 'days') {
3537
            $datedue->set_hour(23);
3537
            $datedue->set_hour(23);
Lines 3570-3583 sub CalcDateDue { Link Here
3570
            }
3570
            }
3571
        }
3571
        }
3572
        if ( C4::Context->preference('useDaysMode') ne 'Days' ) {
3572
        if ( C4::Context->preference('useDaysMode') ne 'Days' ) {
3573
          my $calendar = Koha::Calendar->new( branchcode => $branch );
3573
          my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
3574
          if ( $calendar->is_holiday($datedue) ) {
3574
          if ( $calendar->is_holiday($datedue) ) {
3575
              # Don't return on a closed day
3575
              # Don't return on a closed day
3576
              $datedue = $calendar->prev_open_day( $datedue );
3576
              $datedue = $calendar->prev_open_day( $datedue )->set(hour => 23, minute => 59);
3577
          }
3577
          }
3578
        }
3578
        }
3579
    }
3579
    }
3580
3581
    return $datedue;
3580
    return $datedue;
3582
}
3581
}
3583
3582
(-)a/C4/HoldsQueue.pm (-4 / +4 lines)
Lines 31-37 use C4::Biblio; Link Here
31
use Koha::DateUtils;
31
use Koha::DateUtils;
32
use Koha::Items;
32
use Koha::Items;
33
use Koha::Patrons;
33
use Koha::Patrons;
34
34
use Koha::DiscreteCalendar;
35
use List::Util qw(shuffle);
35
use List::Util qw(shuffle);
36
use List::MoreUtils qw(any);
36
use List::MoreUtils qw(any);
37
use Data::Dumper;
37
use Data::Dumper;
Lines 78-84 sub TransportCostMatrix { Link Here
78
        };
78
        };
79
79
80
        if ( C4::Context->preference("HoldsQueueSkipClosed") ) {
80
        if ( C4::Context->preference("HoldsQueueSkipClosed") ) {
81
            $calendars->{$from} ||= Koha::Calendar->new( branchcode => $from );
81
            $calendars->{$from} ||= Koha::DiscreteCalendar->new({ branchcode => $from });
82
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
82
            $transport_cost_matrix{$to}{$from}{disable_transfer} ||=
83
              $calendars->{$from}->is_holiday( $today );
83
              $calendars->{$from}->is_holiday( $today );
84
        }
84
        }
Lines 205-211 sub CreateQueue { Link Here
205
        $total_requests        += scalar(@$hold_requests);
205
        $total_requests        += scalar(@$hold_requests);
206
        $total_available_items += scalar(@$available_items);
206
        $total_available_items += scalar(@$available_items);
207
207
208
        my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix);
208
       my $item_map = MapItemsToHoldRequests($hold_requests, $available_items, $branches_to_use, $transport_cost_matrix);
209
        $item_map  or next;
209
        $item_map  or next;
210
        my $item_map_size = scalar(keys %$item_map)
210
        my $item_map_size = scalar(keys %$item_map)
211
          or next;
211
          or next;
Lines 755-761 sub load_branches_to_pull_from { Link Here
755
    my $today = dt_from_string();
755
    my $today = dt_from_string();
756
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
756
    if ( C4::Context->preference('HoldsQueueSkipClosed') ) {
757
        @branches_to_use = grep {
757
        @branches_to_use = grep {
758
            !Koha::Calendar->new( branchcode => $_ )
758
            !Koha::DiscreteCalendar->new({ branchcode => $_ })
759
              ->is_holiday( $today )
759
              ->is_holiday( $today )
760
        } @branches_to_use;
760
        } @branches_to_use;
761
    }
761
    }
(-)a/C4/Overdues.pm (-2 / +3 lines)
Lines 34-39 use C4::Accounts; Link Here
34
use C4::Log; # logaction
34
use C4::Log; # logaction
35
use C4::Debug;
35
use C4::Debug;
36
use Koha::DateUtils;
36
use Koha::DateUtils;
37
use Koha::DiscreteCalendar;
37
use Koha::Account::Lines;
38
use Koha::Account::Lines;
38
use Koha::Account::Offsets;
39
use Koha::Account::Offsets;
39
use Koha::IssuingRules;
40
use Koha::IssuingRules;
Lines 294-300 sub get_chargeable_units { Link Here
294
    my $charge_duration;
295
    my $charge_duration;
295
    if ($unit eq 'hours') {
296
    if ($unit eq 'hours') {
296
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
297
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
297
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
298
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
298
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
299
            $charge_duration = $calendar->hours_between( $date_due, $date_returned );
299
        } else {
300
        } else {
300
            $charge_duration = $date_returned->delta_ms( $date_due );
301
            $charge_duration = $date_returned->delta_ms( $date_due );
Lines 306-312 sub get_chargeable_units { Link Here
306
    }
307
    }
307
    else { # days
308
    else { # days
308
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
309
        if(C4::Context->preference('finesCalendar') eq 'noFinesWhenClosed') {
309
            my $calendar = Koha::Calendar->new( branchcode => $branchcode );
310
            my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
310
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
311
            $charge_duration = $calendar->days_between( $date_due, $date_returned );
311
        } else {
312
        } else {
312
            $charge_duration = $date_returned->delta_days( $date_due );
313
            $charge_duration = $date_returned->delta_days( $date_due );
(-)a/C4/Reserves.pm (-2 / +2 lines)
Lines 35-44 use C4::Members::Messaging; Link Here
35
use C4::Members;
35
use C4::Members;
36
use Koha::Account::Lines;
36
use Koha::Account::Lines;
37
use Koha::Biblios;
37
use Koha::Biblios;
38
use Koha::Calendar;
39
use Koha::CirculationRules;
38
use Koha::CirculationRules;
40
use Koha::Database;
39
use Koha::Database;
41
use Koha::DateUtils;
40
use Koha::DateUtils;
41
use Koha::DiscreteCalendar;
42
use Koha::Hold;
42
use Koha::Hold;
43
use Koha::Holds;
43
use Koha::Holds;
44
use Koha::IssuingRules;
44
use Koha::IssuingRules;
Lines 837-843 sub CancelExpiredReserves { Link Here
837
    my $holds = Koha::Holds->search( $params );
837
    my $holds = Koha::Holds->search( $params );
838
838
839
    while ( my $hold = $holds->next ) {
839
    while ( my $hold = $holds->next ) {
840
        my $calendar = Koha::Calendar->new( branchcode => $hold->branchcode );
840
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $hold->branchcode });
841
841
842
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
842
        next if !$cancel_on_holidays && $calendar->is_holiday( $today );
843
843
(-)a/Koha/Calendar.pm (-535 lines)
Lines 1-535 Link Here
1
package Koha::Calendar;
2
use strict;
3
use warnings;
4
use 5.010;
5
6
use DateTime;
7
use DateTime::Set;
8
use DateTime::Duration;
9
use C4::Context;
10
use Koha::Caches;
11
use Carp;
12
13
sub new {
14
    my ( $classname, %options ) = @_;
15
    my $self = {};
16
    bless $self, $classname;
17
    for my $o_name ( keys %options ) {
18
        my $o = lc $o_name;
19
        $self->{$o} = $options{$o_name};
20
    }
21
    if ( !defined $self->{branchcode} ) {
22
        croak 'No branchcode argument passed to Koha::Calendar->new';
23
    }
24
    $self->_init();
25
    return $self;
26
}
27
28
sub _init {
29
    my $self       = shift;
30
    my $branch     = $self->{branchcode};
31
    my $dbh        = C4::Context->dbh();
32
    my $weekly_closed_days_sth = $dbh->prepare(
33
'SELECT weekday FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NOT NULL'
34
    );
35
    $weekly_closed_days_sth->execute( $branch );
36
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];
37
    while ( my $tuple = $weekly_closed_days_sth->fetchrow_hashref ) {
38
        $self->{weekly_closed_days}->[ $tuple->{weekday} ] = 1;
39
    }
40
    my $day_month_closed_days_sth = $dbh->prepare(
41
'SELECT day, month FROM repeatable_holidays WHERE branchcode = ? AND weekday IS NULL'
42
    );
43
    $day_month_closed_days_sth->execute( $branch );
44
    $self->{day_month_closed_days} = {};
45
    while ( my $tuple = $day_month_closed_days_sth->fetchrow_hashref ) {
46
        $self->{day_month_closed_days}->{ $tuple->{month} }->{ $tuple->{day} } =
47
          1;
48
    }
49
50
    $self->{days_mode}       ||= C4::Context->preference('useDaysMode');
51
    $self->{test}            = 0;
52
    return;
53
}
54
55
sub exception_holidays {
56
    my ( $self ) = @_;
57
58
    my $cache  = Koha::Caches->get_instance();
59
    my $cached = $cache->get_from_cache('exception_holidays');
60
    return $cached if $cached;
61
62
    my $dbh = C4::Context->dbh;
63
    my $branch = $self->{branchcode};
64
    my $exception_holidays_sth = $dbh->prepare(
65
'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 1'
66
    );
67
    $exception_holidays_sth->execute( $branch );
68
    my $dates = [];
69
    while ( my ( $day, $month, $year ) = $exception_holidays_sth->fetchrow ) {
70
        push @{$dates},
71
          DateTime->new(
72
            day       => $day,
73
            month     => $month,
74
            year      => $year,
75
            time_zone => "floating",
76
          )->truncate( to => 'day' );
77
    }
78
    $self->{exception_holidays} =
79
      DateTime::Set->from_datetimes( dates => $dates );
80
    $cache->set_in_cache( 'exception_holidays', $self->{exception_holidays} );
81
    return $self->{exception_holidays};
82
}
83
84
sub single_holidays {
85
    my ( $self, $date ) = @_;
86
    my $branchcode = $self->{branchcode};
87
    my $cache           = Koha::Caches->get_instance();
88
    my $single_holidays = $cache->get_from_cache('single_holidays');
89
90
    # $single_holidays looks like:
91
    # {
92
    #   CPL =>  [
93
    #        [0] 20131122,
94
    #         ...
95
    #    ],
96
    #   ...
97
    # }
98
99
    unless ($single_holidays) {
100
        my $dbh = C4::Context->dbh;
101
        $single_holidays = {};
102
103
        # push holidays for each branch
104
        my $branches_sth =
105
          $dbh->prepare('SELECT distinct(branchcode) FROM special_holidays');
106
        $branches_sth->execute();
107
        while ( my $br = $branches_sth->fetchrow ) {
108
            my $single_holidays_sth = $dbh->prepare(
109
'SELECT day, month, year FROM special_holidays WHERE branchcode = ? AND isexception = 0'
110
            );
111
            $single_holidays_sth->execute($br);
112
113
            my @ymd_arr;
114
            while ( my ( $day, $month, $year ) =
115
                $single_holidays_sth->fetchrow )
116
            {
117
                my $dt = DateTime->new(
118
                    day       => $day,
119
                    month     => $month,
120
                    year      => $year,
121
                    time_zone => 'floating',
122
                )->truncate( to => 'day' );
123
                push @ymd_arr, $dt->ymd('');
124
            }
125
            $single_holidays->{$br} = \@ymd_arr;
126
        }    # br
127
        $cache->set_in_cache( 'single_holidays', $single_holidays,
128
            { expiry => 76800 } )    #24 hrs ;
129
    }
130
    my $holidays  = ( $single_holidays->{$branchcode} );
131
    for my $hols  (@$holidays ) {
132
            return 1 if ( $date == $hols )   #match ymds;
133
    }
134
    return 0;
135
}
136
137
sub addDate {
138
    my ( $self, $startdate, $add_duration, $unit ) = @_;
139
140
    # Default to days duration (legacy support I guess)
141
    if ( ref $add_duration ne 'DateTime::Duration' ) {
142
        $add_duration = DateTime::Duration->new( days => $add_duration );
143
    }
144
145
    $unit ||= 'days'; # default days ?
146
    my $dt;
147
148
    if ( $unit eq 'hours' ) {
149
        # Fixed for legacy support. Should be set as a branch parameter
150
        my $return_by_hour = 10;
151
152
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
153
    } else {
154
        # days
155
        $dt = $self->addDays($startdate, $add_duration);
156
    }
157
158
    return $dt;
159
}
160
161
sub addHours {
162
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
163
    my $base_date = $startdate->clone();
164
165
    $base_date->add_duration($hours_duration);
166
167
    # If we are using the calendar behave for now as if Datedue
168
    # was the chosen option (current intended behaviour)
169
170
    if ( $self->{days_mode} ne 'Days' &&
171
          $self->is_holiday($base_date) ) {
172
173
        if ( $hours_duration->is_negative() ) {
174
            $base_date = $self->prev_open_day($base_date);
175
        } else {
176
            $base_date = $self->next_open_day($base_date);
177
        }
178
179
        $base_date->set_hour($return_by_hour);
180
181
    }
182
183
    return $base_date;
184
}
185
186
sub addDays {
187
    my ( $self, $startdate, $days_duration ) = @_;
188
    my $base_date = $startdate->clone();
189
190
    $self->{days_mode} ||= q{};
191
192
    if ( $self->{days_mode} eq 'Calendar' ) {
193
        # use the calendar to skip all days the library is closed
194
        # when adding
195
        my $days = abs $days_duration->in_units('days');
196
197
        if ( $days_duration->is_negative() ) {
198
            while ($days) {
199
                $base_date = $self->prev_open_day($base_date);
200
                --$days;
201
            }
202
        } else {
203
            while ($days) {
204
                $base_date = $self->next_open_day($base_date);
205
                --$days;
206
            }
207
        }
208
209
    } else { # Days or Datedue
210
        # use straight days, then use calendar to push
211
        # the date to the next open day if Datedue
212
        $base_date->add_duration($days_duration);
213
214
        if ( $self->{days_mode} eq 'Datedue' ) {
215
            # Datedue, then use the calendar to push
216
            # the date to the next open day if holiday
217
            if ( $self->is_holiday($base_date) ) {
218
219
                if ( $days_duration->is_negative() ) {
220
                    $base_date = $self->prev_open_day($base_date);
221
                } else {
222
                    $base_date = $self->next_open_day($base_date);
223
                }
224
            }
225
        }
226
    }
227
228
    return $base_date;
229
}
230
231
sub is_holiday {
232
    my ( $self, $dt ) = @_;
233
234
    my $localdt = $dt->clone();
235
    my $day   = $localdt->day;
236
    my $month = $localdt->month;
237
238
    #Change timezone to "floating" before doing any calculations or comparisons
239
    $localdt->set_time_zone("floating");
240
    $localdt->truncate( to => 'day' );
241
242
243
    if ( $self->exception_holidays->contains($localdt) ) {
244
        # exceptions are not holidays
245
        return 0;
246
    }
247
248
    my $dow = $localdt->day_of_week;
249
    # Representation fix
250
    # DateTime object dow (1-7) where Monday is 1
251
    # Arrays are 0-based where 0 = Sunday, not 7.
252
    if ( $dow == 7 ) {
253
        $dow = 0;
254
    }
255
256
    if ( $self->{weekly_closed_days}->[$dow] == 1 ) {
257
        return 1;
258
    }
259
260
    if ( exists $self->{day_month_closed_days}->{$month}->{$day} ) {
261
        return 1;
262
    }
263
264
    my $ymd   = $localdt->ymd('')  ;
265
    if ($self->single_holidays(  $ymd  ) == 1 ) {
266
        return 1;
267
    }
268
269
    # damn have to go to work after all
270
    return 0;
271
}
272
273
sub next_open_day {
274
    my ( $self, $dt ) = @_;
275
    my $base_date = $dt->clone();
276
277
    $base_date->add(days => 1);
278
279
    while ($self->is_holiday($base_date)) {
280
        $base_date->add(days => 1);
281
    }
282
283
    return $base_date;
284
}
285
286
sub prev_open_day {
287
    my ( $self, $dt ) = @_;
288
    my $base_date = $dt->clone();
289
290
    $base_date->add(days => -1);
291
292
    while ($self->is_holiday($base_date)) {
293
        $base_date->add(days => -1);
294
    }
295
296
    return $base_date;
297
}
298
299
sub days_forward {
300
    my $self     = shift;
301
    my $start_dt = shift;
302
    my $num_days = shift;
303
304
    return $start_dt unless $num_days > 0;
305
306
    my $base_dt = $start_dt->clone();
307
308
    while ($num_days--) {
309
        $base_dt = $self->next_open_day($base_dt);
310
    }
311
312
    return $base_dt;
313
}
314
315
sub days_between {
316
    my $self     = shift;
317
    my $start_dt = shift;
318
    my $end_dt   = shift;
319
320
    # Change time zone for date math and swap if needed
321
    $start_dt = $start_dt->clone->set_time_zone('floating');
322
    $end_dt = $end_dt->clone->set_time_zone('floating');
323
    if( $start_dt->compare($end_dt) > 0 ) {
324
        ( $start_dt, $end_dt ) = ( $end_dt, $start_dt );
325
    }
326
327
    # start and end should not be closed days
328
    my $days = $start_dt->delta_days($end_dt)->delta_days;
329
    while( $start_dt->compare($end_dt) < 1 ) {
330
        $days-- if $self->is_holiday($start_dt);
331
        $start_dt->add( days => 1 );
332
    }
333
    return DateTime::Duration->new( days => $days );
334
}
335
336
sub hours_between {
337
    my ($self, $start_date, $end_date) = @_;
338
    my $start_dt = $start_date->clone()->set_time_zone('floating');
339
    my $end_dt = $end_date->clone()->set_time_zone('floating');
340
    my $duration = $end_dt->delta_ms($start_dt);
341
    $start_dt->truncate( to => 'day' );
342
    $end_dt->truncate( to => 'day' );
343
    # NB this is a kludge in that it assumes all days are 24 hours
344
    # However for hourly loans the logic should be expanded to
345
    # take into account open/close times then it would be a duration
346
    # of library open hours
347
    my $skipped_days = 0;
348
    for (my $dt = $start_dt->clone();
349
        $dt <= $end_dt;
350
        $dt->add(days => 1)
351
    ) {
352
        if ($self->is_holiday($dt)) {
353
            ++$skipped_days;
354
        }
355
    }
356
    if ($skipped_days) {
357
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
358
    }
359
360
    return $duration;
361
362
}
363
364
sub set_daysmode {
365
    my ( $self, $mode ) = @_;
366
367
    # if not testing this is a no op
368
    if ( $self->{test} ) {
369
        $self->{days_mode} = $mode;
370
    }
371
372
    return;
373
}
374
375
sub clear_weekly_closed_days {
376
    my $self = shift;
377
    $self->{weekly_closed_days} = [ 0, 0, 0, 0, 0, 0, 0 ];    # Sunday only
378
    return;
379
}
380
381
1;
382
__END__
383
384
=head1 NAME
385
386
Koha::Calendar - Object containing a branches calendar
387
388
=head1 SYNOPSIS
389
390
  use Koha::Calendar
391
392
  my $c = Koha::Calendar->new( branchcode => 'MAIN' );
393
  my $dt = DateTime->now();
394
395
  # are we open
396
  $open = $c->is_holiday($dt);
397
  # when will item be due if loan period = $dur (a DateTime::Duration object)
398
  $duedate = $c->addDate($dt,$dur,'days');
399
400
401
=head1 DESCRIPTION
402
403
  Implements those features of C4::Calendar needed for Staffs Rolling Loans
404
405
=head1 METHODS
406
407
=head2 new : Create a calendar object
408
409
my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
410
411
The option branchcode is required
412
413
414
=head2 addDate
415
416
    my $dt = $calendar->addDate($date, $dur, $unit)
417
418
C<$date> is a DateTime object representing the starting date of the interval.
419
420
C<$offset> is a DateTime::Duration to add to it
421
422
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
423
424
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
425
parameter will be removed when issuingrules properly cope with that
426
427
428
=head2 addHours
429
430
    my $dt = $calendar->addHours($date, $dur, $return_by_hour )
431
432
C<$date> is a DateTime object representing the starting date of the interval.
433
434
C<$offset> is a DateTime::Duration to add to it
435
436
C<$return_by_hour> is an integer value representing the opening hour for the branch
437
438
439
=head2 addDays
440
441
    my $dt = $calendar->addDays($date, $dur)
442
443
C<$date> is a DateTime object representing the starting date of the interval.
444
445
C<$offset> is a DateTime::Duration to add to it
446
447
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
448
449
Currently unit is only used to invoke Staffs return Monday at 10 am rule this
450
parameter will be removed when issuingrules properly cope with that
451
452
453
=head2 single_holidays
454
455
my $rc = $self->single_holidays(  $ymd  );
456
457
Passed a $date in Ymd (yyyymmdd) format -  returns 1 if date is a single_holiday, or 0 if not.
458
459
460
=head2 is_holiday
461
462
$yesno = $calendar->is_holiday($dt);
463
464
passed a DateTime object returns 1 if it is a closed day
465
0 if not according to the calendar
466
467
=head2 days_between
468
469
$duration = $calendar->days_between($start_dt, $end_dt);
470
471
Passed two dates returns a DateTime::Duration object measuring the length between them
472
ignoring closed days. Always returns a positive number irrespective of the
473
relative order of the parameters
474
475
=head2 next_open_day
476
477
$datetime = $calendar->next_open_day($duedate_dt)
478
479
Passed a Datetime returns another Datetime representing the next open day. It is
480
intended for use to calculate the due date when useDaysMode syspref is set to either
481
'Datedue' or 'Calendar'.
482
483
=head2 prev_open_day
484
485
$datetime = $calendar->prev_open_day($duedate_dt)
486
487
Passed a Datetime returns another Datetime representing the previous open day. It is
488
intended for use to calculate the due date when useDaysMode syspref is set to either
489
'Datedue' or 'Calendar'.
490
491
=head2 set_daysmode
492
493
For testing only allows the calling script to change days mode
494
495
=head2 clear_weekly_closed_days
496
497
In test mode changes the testing set of closed days to a new set with
498
no closed days. TODO passing an array of closed days to this would
499
allow testing of more configurations
500
501
=head2 add_holiday
502
503
Passed a datetime object this will add it to the calendar's list of
504
closed days. This is for testing so that we can alter the Calenfar object's
505
list of specified dates
506
507
=head1 DIAGNOSTICS
508
509
Will croak if not passed a branchcode in new
510
511
=head1 BUGS AND LIMITATIONS
512
513
This only contains a limited subset of the functionality in C4::Calendar
514
Only enough to support Staffs Rolling loans
515
516
=head1 AUTHOR
517
518
Colin Campbell colin.campbell@ptfs-europe.com
519
520
=head1 LICENSE AND COPYRIGHT
521
522
Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
523
524
This program is free software: you can redistribute it and/or modify
525
it under the terms of the GNU General Public License as published by
526
the Free Software Foundation, either version 2 of the License, or
527
(at your option) any later version.
528
529
This program is distributed in the hope that it will be useful,
530
but WITHOUT ANY WARRANTY; without even the implied warranty of
531
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
532
GNU General Public License for more details.
533
534
You should have received a copy of the GNU General Public License
535
along with this program.  If not, see <http://www.gnu.org/licenses/>.
(-)a/Koha/DiscreteCalendar.pm (+1317 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 = DateTime->now();
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->addDate($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
    $self->_init();
87
88
    return $self;
89
}
90
91
sub _init {
92
    my $self = shift;
93
    $self->{days_mode} ||= C4::Context->preference('useDaysMode');
94
    #If the branchcode doesn't exist we use the default calendar.
95
    my $schema = Koha::Database->new->schema;
96
    my $branchcode = $self->{branchcode};
97
    my $dtf = $schema->storage->datetime_parser;
98
    my $today = $dtf->format_datetime(DateTime->today);
99
    my $rs = $schema->resultset('DiscreteCalendar')->single(
100
        {
101
            branchcode  => $branchcode,
102
            date        => $today
103
        }
104
    );
105
    #use default if no calendar is found
106
    if (!$rs){
107
        $self->{branchcode} = '';
108
        $self->{no_branch_selected} = 1;
109
    }
110
111
}
112
113
=head2 get_dates_info
114
115
  my @dates = $calendar->get_dates_info();
116
117
Returns an array of hashes representing the dates in this calendar. The hash
118
contains the fields C<$date>, C<$outputdate>, C<$holiday_type>, C<$open_hour>,
119
C<$close_hour> and C<$note>.
120
121
=cut
122
123
sub get_dates_info {
124
    my $self = shift;
125
    my $branchcode = $self->{branchcode};
126
    my @datesInfos =();
127
    my $schema = Koha::Database->new->schema;
128
129
    my $rs = $schema->resultset('DiscreteCalendar')->search(
130
        {
131
            branchcode  => $branchcode
132
        },
133
        {
134
            select  => [ 'date', { DATE => 'date' } ],
135
            as      => [qw/ date date /],
136
            columns =>[ qw/ holiday_type open_hour close_hour note/]
137
        },
138
    );
139
140
    while (my $date = $rs->next()){
141
        my $outputdate = dt_from_string( $date->date(), 'iso');
142
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
143
        push @datesInfos, {
144
            date        => $date->date(),
145
            outputdate  => $outputdate,
146
            holiday_type => $date->holiday_type() ,
147
            open_hour    => $date->open_hour(),
148
            close_hour   => $date->close_hour(),
149
            note        => $date->note()
150
        };
151
    }
152
153
    return @datesInfos;
154
}
155
156
=head2 add_new_branch
157
158
    Koha::DiscreteCalendar->add_new_branch($copyBranch, $newBranch)
159
160
This method will copy everything from a given branch to a new branch
161
C<$copyBranch> is the branch to copy from
162
C<$newBranch> is the branch to be created, and copy into
163
164
=cut
165
166
sub add_new_branch {
167
    my ( $classname, $copyBranch, $newBranch) = @_;
168
169
    $copyBranch = '' unless $copyBranch;
170
    my $schema = Koha::Database->new->schema;
171
172
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
173
            branchcode => $copyBranch
174
    });
175
176
    while(my $row = $branch_rs->next()){
177
        $schema->resultset('DiscreteCalendar')->create({
178
            date        => $row->date(),
179
            branchcode  => $newBranch,
180
            is_opened    => $row->is_opened(),
181
            holiday_type => $row->holiday_type(),
182
            open_hour    => $row->open_hour(),
183
            close_hour   => $row->close_hour(),
184
        });
185
    }
186
187
}
188
189
=head2 get_date_info
190
191
    my $date = $calendar->get_date_info;
192
193
Returns a reference-to-hash representing a DiscreteCalendar date data object.
194
The hash contains the fields C<$date>, C<$outputdate>, C<$holiday_type>,
195
C<$open_hour>, C<$close_hour> and C<$note>.
196
197
=cut
198
199
sub get_date_info {
200
    my ($self, $date) = @_;
201
    my $branchcode = $self->{branchcode};
202
    my $schema = Koha::Database->new->schema;
203
    my $dtf = $schema->storage->datetime_parser;
204
    #String dates for Database usage
205
    my $date_string = $dtf->format_datetime($date);
206
207
    my $rs = $schema->resultset('DiscreteCalendar')->search(
208
        {
209
            branchcode  => $branchcode,
210
        },
211
        {
212
            select  => [ 'date', { DATE => 'date' } ],
213
            as      => [qw/ date date /],
214
            where   => \['DATE(?) = date', $date_string ],
215
            columns =>[ qw/ branchcode holiday_type open_hour close_hour note/]
216
        },
217
    );
218
    my $dateDTO;
219
    while (my $date = $rs->next()){
220
        $dateDTO = {
221
            date        => $date->date(),
222
            branchcode  => $date->branchcode(),
223
            holiday_type => $date->holiday_type() ,
224
            open_hour    => $date->open_hour(),
225
            close_hour   => $date->close_hour(),
226
            note        => $date->note()
227
        };
228
    }
229
230
    return $dateDTO;
231
}
232
233
=head2 get_max_date
234
235
    my $maxDate = $calendar->get_max_date();
236
237
Returns the furthest date available in the databse of current branch.
238
239
=cut
240
241
sub get_max_date {
242
    my $self       = shift;
243
    my $branchcode = $self->{branchcode};
244
    my $schema = Koha::Database->new->schema;
245
246
    my $rs = $schema->resultset('DiscreteCalendar')->search(
247
        {
248
            branchcode  => $branchcode
249
        },
250
        {
251
            select => [{ MAX => 'date' } ],
252
            as     => [qw/ max /],
253
        }
254
    );
255
256
    return $rs->next()->get_column('max');
257
}
258
259
=head2 get_min_date
260
261
    my $minDate = $calendar->get_min_date();
262
263
Returns the oldest date available in the databse of current branch.
264
265
=cut
266
267
sub get_min_date {
268
    my $self       = shift;
269
    my $branchcode     = $self->{branchcode};
270
    my $schema = Koha::Database->new->schema;
271
272
    my $rs = $schema->resultset('DiscreteCalendar')->search(
273
        {
274
            branchcode  => $branchcode
275
        },
276
        {
277
            select => [{ MIN => 'date' } ],
278
            as     => [qw/ min /],
279
        }
280
    );
281
282
    return $rs->next()->get_column('min');
283
}
284
285
=head2 get_unique_holidays
286
287
  my @unique_holidays = $calendar->get_unique_holidays();
288
289
Returns an array of all the date objects that are unique holidays.
290
291
=cut
292
293
sub get_unique_holidays {
294
    my $self = shift;
295
    my $exclude_past = shift || 1;
296
    my $branchcode = $self->{branchcode};
297
    my @unique_holidays;
298
    my $schema = Koha::Database->new->schema;
299
300
    my $rs = $schema->resultset('DiscreteCalendar')->search(
301
        {
302
            branchcode  => $branchcode,
303
            holiday_type => $HOLIDAYS->{EXCEPTION}
304
        },
305
        {
306
            select => [{ DATE => 'date' }, 'note' ],
307
            as     => [qw/ date note/],
308
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
309
        }
310
    );
311
    while (my $date = $rs->next()){
312
        my $outputdate = dt_from_string($date->date(), 'iso');
313
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
314
        push @unique_holidays, {
315
            date =>  $date->date(),
316
            outputdate => $outputdate,
317
            note => $date->note()
318
        }
319
    }
320
321
    return @unique_holidays;
322
}
323
324
=head2 get_float_holidays
325
326
  my @float_holidays = $calendar->get_float_holidays();
327
328
Returns an array of all the date objects that are float holidays.
329
330
=cut
331
332
sub get_float_holidays {
333
    my $self = shift;
334
    my $exclude_past = shift || 1;
335
    my $branchcode = $self->{branchcode};
336
    my @float_holidays;
337
    my $schema = Koha::Database->new->schema;
338
339
    my $rs = $schema->resultset('DiscreteCalendar')->search(
340
        {
341
            branchcode  => $branchcode,
342
            holiday_type => $HOLIDAYS->{FLOAT}
343
        },
344
        {
345
            select => [{ DATE => 'date' }, 'note' ],
346
            as     => [qw/ date note/],
347
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
348
        }
349
    );
350
    while (my $date = $rs->next()){
351
        my $outputdate = dt_from_string($date->date(), 'iso');
352
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
353
        push @float_holidays, {
354
            date        =>  $date->date(),
355
            outputdate  => $outputdate,
356
            note        => $date->note()
357
        }
358
    }
359
360
    return @float_holidays;
361
}
362
363
=head2 get_need_validation_holidays
364
365
  my @need_validation_holidays = $calendar->get_need_validation_holidays();
366
367
Returns an array of all the date objects that are float holidays in need of validation.
368
369
=cut
370
371
sub get_need_validation_holidays {
372
    my $self = shift;
373
    my $exclude_past = shift || 1;
374
    my $branchcode = $self->{branchcode};
375
    my @need_validation_holidays;
376
    my $schema = Koha::Database->new->schema;
377
378
    my $rs = $schema->resultset('DiscreteCalendar')->search(
379
        {
380
            branchcode  => $branchcode,
381
            holiday_type => $HOLIDAYS->{NEED_VALIDATION}
382
        },
383
        {
384
            select => [{ DATE => 'date' }, 'note' ],
385
            as     => [qw/ date note/],
386
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
387
        }
388
    );
389
    while (my $date = $rs->next()){
390
        my $outputdate = dt_from_string($date->date(), 'iso');
391
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
392
        push @need_validation_holidays, {
393
            date        =>  $date->date(),
394
            outputdate  => $outputdate,
395
            note        => $date->note()
396
        }
397
    }
398
399
    return @need_validation_holidays;
400
}
401
402
=head2 get_repeatable_holidays
403
404
  my @repeatable_holidays = $calendar->get_repeatable_holidays();
405
406
Returns an array of all the date objects that are repeatable holidays.
407
408
=cut
409
410
sub get_repeatable_holidays {
411
    my $self = shift;
412
    my $exclude_past = shift || 1;
413
    my $branchcode = $self->{branchcode};
414
    my @repeatable_holidays;
415
    my $schema = Koha::Database->new->schema;
416
417
    my $rs = $schema->resultset('DiscreteCalendar')->search(
418
        {
419
            branchcode  => $branchcode,
420
            holiday_type => $HOLIDAYS->{'REPEATABLE'},
421
422
        },
423
        {
424
            select  => \[ 'distinct DAY(date), MONTH(date), note'],
425
            as      => [qw/ day month note/],
426
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
427
        }
428
    );
429
430
    while (my $date = $rs->next()){
431
        push @repeatable_holidays, {
432
            day=> $date->get_column('day'),
433
            month => $date->get_column('month'),
434
            note => $date->note()
435
        };
436
    }
437
438
    return @repeatable_holidays;
439
}
440
441
=head2 get_week_days_holidays
442
443
  my @week_days_holidays = $calendar->get_week_days_holidays;
444
445
Returns an array of all the date objects that are weekly holidays.
446
447
=cut
448
449
sub get_week_days_holidays {
450
    my $self = shift;
451
    my $exclude_past = shift || 1;
452
    my $branchcode = $self->{branchcode};
453
    my @week_days;
454
    my $schema = Koha::Database->new->schema;
455
456
    my $rs = $schema->resultset('DiscreteCalendar')->search(
457
        {
458
            holiday_type => $HOLIDAYS->{WEEKLY},
459
            branchcode  => $branchcode,
460
        },
461
        {
462
            select      => [{ DAYOFWEEK => 'date'}, 'note'],
463
            as          => [qw/ weekday note /],
464
            distinct    => 1,
465
            where  => ($exclude_past ? \[' date >= CURRENT_DATE()'] : {} ),
466
        }
467
    );
468
469
    while (my $date = $rs->next()){
470
        push @week_days, {
471
            weekday => ($date->get_column('weekday') -1),
472
            note    => $date->note()
473
        };
474
    }
475
476
    return @week_days;
477
}
478
479
=head2 edit_holiday
480
481
Modifies a date or a range of dates
482
483
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
484
485
C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range.
486
487
C<$holiday_type> Is the type of the holiday :
488
    E : Exception holiday, single day.
489
    F : Floating holiday, different day each year.
490
    N : Needs validation, copied float holiday from the past
491
    R : Repeatable holiday, repeated on same date.
492
    W : Weekly holiday, same day of the week.
493
494
C<$open_hour> Is the opening hour.
495
C<$close_hour> Is the closing hour.
496
C<$start_date> Is the start of the range of dates.
497
C<$end_date> Is the end of the range of dates.
498
C<$delete_type> Delete all
499
C<$today> Today based on the local date, using JavaScript.
500
501
=cut
502
503
sub edit_holiday {
504
    my $self = shift;
505
    my ($params) = @_;
506
507
    my $title        = $params->{title};
508
    my $weekday      = $params->{weekday} || '';
509
    my $holiday_type = $params->{holiday_type};
510
511
    my $start_date   = $params->{start_date};
512
    my $end_date     = $params->{end_date};
513
514
    my $open_hour    = $params->{open_hour} || '';
515
    my $close_hour   = $params->{close_hour} || '';
516
517
    my $delete_type  = $params->{delete_type} || undef;
518
    my $today        = $params->{today} || dt_from_string()->truncate( to => 'day' );
519
520
    my $branchcode = $self->{branchcode};
521
522
    # When override param is set, this function will allow past dates to be set as holidays,
523
    # otherwise it will not. This is meant to only be used for testing.
524
    my $override = $params->{override} || 0;
525
526
    my $schema = Koha::Database->new->schema;
527
    $schema->{AutoCommit} = 0;
528
    $schema->storage->txn_begin;
529
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
530
531
    #String dates for Database usage
532
    my $start_date_string = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
533
    my $end_date_string = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
534
    $today = $dtf->format_datetime($today->clone->truncate(to => 'day'));
535
    my %updateValues = (
536
        is_opened    => 0,
537
        note         => $title,
538
        holiday_type => $holiday_type,
539
    );
540
    $updateValues{open_hour}  = $open_hour if $open_hour ne '';
541
    $updateValues{close_hour}  = $close_hour if $close_hour ne '';
542
543
    if($holiday_type eq $HOLIDAYS->{WEEKLY}) {
544
        #Update weekly holidays
545
        if($start_date_string eq $end_date_string ){
546
            $end_date_string = $self->get_max_date();
547
        }
548
        my $rs = $schema->resultset('DiscreteCalendar')->search(
549
            {
550
                branchcode  => $branchcode,
551
            },
552
            {
553
                where => \[ 'DAYOFWEEK(date) = ? AND date >= DATE(?) AND date <= DATE(?)', $weekday, $start_date_string, $end_date_string],
554
            }
555
        );
556
557
        while (my $date = $rs->next()){
558
            $date->update(\%updateValues);
559
        }
560
    }elsif ($holiday_type eq $HOLIDAYS->{EXCEPTION} || $holiday_type eq $HOLIDAYS->{FLOAT} || $holiday_type eq $HOLIDAYS->{NEED_VALIDATION}) {
561
        #Update Exception Float and Needs Validation holidays
562
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
563
        if($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday'){
564
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
565
        }
566
        $where->{date}{'>='} = $today unless $override;
567
568
        my $rs = $schema->resultset('DiscreteCalendar')->search(
569
            {
570
                branchcode  => $branchcode,
571
            },
572
            {
573
                where =>  $where,
574
            }
575
        );
576
        while (my $date = $rs->next()){
577
            $date->update(\%updateValues);
578
        }
579
580
    }elsif ($holiday_type eq $HOLIDAYS->{REPEATABLE}) {
581
        #Update repeatable holidays
582
        my $parser = DateTime::Format::Strptime->new(
583
           pattern  => '%m-%d',
584
           on_error => 'croak',
585
        );
586
        #Format the dates to have only month-day ex: 01-04 for January 4th
587
        $start_date = $parser->format_datetime($start_date);
588
        $end_date = $parser->format_datetime($end_date);
589
        my $where = { -and => [ \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ?)", $start_date, $end_date] ] };
590
        push @{$where->{'-and'}}, { 'date' => { '>=' => $today } } unless $override;
591
        my $rs = $schema->resultset('DiscreteCalendar')->search(
592
            {
593
                branchcode  => $branchcode,
594
            },
595
            {
596
                where => $where,
597
            }
598
        );
599
        while (my $date = $rs->next()){
600
            $date->update(\%updateValues);
601
        }
602
603
    }else {
604
        #Update date(s)/Remove holidays
605
        my $where = { date => { -between => [$start_date_string, $end_date_string]}};
606
        if($start_date_string ne $end_date_string && $weekday && $weekday ne 'everyday'){
607
            $where = {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { -between => [$start_date_string, $end_date_string]}]};
608
        }
609
        $where->{date}{'>='} = $today unless $override;
610
611
        my $rs = $schema->resultset('DiscreteCalendar')->search(
612
            {
613
                branchcode  => $branchcode,
614
            },
615
            {
616
                where =>  $where,
617
            }
618
        );
619
        #If none, the date(s) will be normal days, else,
620
        if($holiday_type eq 'none'){
621
            $updateValues{holiday_type}  ='';
622
            $updateValues{is_opened}  =1;
623
        }else{
624
            delete $updateValues{holiday_type};
625
            delete $updateValues{is_opened};
626
        }
627
628
        while (my $date = $rs->next()){
629
            if($delete_type){
630
                if($date->holiday_type() eq $HOLIDAYS->{WEEKLY}){
631
                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
632
                }elsif($date->holiday_type() eq $HOLIDAYS->{REPEATABLE}){
633
                    $self->remove_repeatable_holidays($start_date, $end_date, \%updateValues, $today);
634
                }
635
            }else{
636
                $date->update(\%updateValues);
637
            }
638
        }
639
    }
640
    $schema->storage->txn_commit;
641
}
642
643
=head2 remove_weekly_holidays
644
645
    $calendar->remove_weekly_holidays($weekday, $updateValues, $today);
646
647
Removes a weekly holiday and updates the days' parameters
648
C<$weekday> is the weekday to un-holiday
649
C<$updatevalues> is hashref containing the new parameters
650
C<$today> is today's date
651
652
=cut
653
654
sub remove_weekly_holidays {
655
    my ($self, $weekday, $updateValues, $today) = @_;
656
    my $branchcode = $self->{branchcode};
657
    my $schema = Koha::Database->new->schema;
658
659
    my $rs = $schema->resultset('DiscreteCalendar')->search(
660
        {
661
            branchcode  => $branchcode,
662
            is_opened    => 0,
663
            holiday_type => $HOLIDAYS->{WEEKLY}
664
        },
665
        {
666
            where => {-and => [ \["DAYOFWEEK(date) = ?", $weekday], date => { '>=' => $today}]},
667
        }
668
    );
669
670
    while (my $date = $rs->next()){
671
        $date->update($updateValues);
672
    }
673
}
674
675
=head2 remove_repeatable_holidays
676
677
    $calendar->remove_repeatable_holidays($startDate, $endDate, $today);
678
679
Removes a repeatable holiday and updates the days' parameters
680
C<$startDatey> is the start date of the repeatable holiday
681
C<$endDate> is the end date of the repeatble holiday
682
C<$updatevalues> is hashref containing the new parameters
683
C<$today> is today's date
684
685
=cut
686
687
sub remove_repeatable_holidays {
688
    my ($self, $startDate, $endDate, $updateValues, $today) = @_;
689
    my $branchcode = $self->{branchcode};
690
    my $schema = Koha::Database->new->schema;
691
    my $parser = DateTime::Format::Strptime->new(
692
        pattern   => '%m-%d',
693
        on_error  => 'croak',
694
    );
695
    #Format the dates to have only month-day ex: 01-04 for January 4th
696
    $startDate = $parser->format_datetime($startDate);
697
    $endDate = $parser->format_datetime($endDate);
698
699
    my $rs = $schema->resultset('DiscreteCalendar')->search(
700
        {
701
            branchcode  => $branchcode,
702
            is_opened    => 0,
703
            holiday_type => $HOLIDAYS->{REPEATABLE},
704
        },
705
        {
706
            where =>  { -and => [date => { '>=' => $today}, \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ?)", $startDate, $endDate]]},
707
        }
708
    );
709
710
    while (my $date = $rs->next()){
711
        $date->update($updateValues);
712
    }
713
}
714
715
=head2 copy_to_branch
716
717
  $calendar->copy_to_branch($branch2);
718
719
Copies the days and holidays from this branch to $branch2, ignoring dates in C<$self>
720
but not in C<$branch2>
721
722
C<$branch2> the branch to copy into
723
724
=cut
725
726
sub copy_to_branch {
727
    my ($self,$newBranch) =@_;
728
    my $branchcode = $self->{branchcode};
729
    my $schema = Koha::Database->new->schema;
730
731
    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
732
        {
733
            branchcode  => $branchcode
734
        },
735
        {
736
            columns     => [ qw/ date is_opened note holiday_type open_hour close_hour /]
737
        }
738
    );
739
    while (my $copyDate = $copyFrom->next()){
740
        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
741
            {
742
                branchcode  => $newBranch,
743
                date        => $copyDate->date(),
744
            },
745
            {
746
                columns => [ qw/ date branchcode is_opened note holiday_type open_hour close_hour /]
747
            }
748
        );
749
        #if the date does not exist in the copyTO branch, than skip it.
750
        if($copyTo->count ==0){
751
            next;
752
        }
753
        $copyTo->next()->update({
754
            is_opened    => $copyDate->is_opened(),
755
            holiday_type => $copyDate->holiday_type(),
756
            note        => $copyDate->note(),
757
            open_hour    => $copyDate->open_hour(),
758
            close_hour   => $copyDate->close_hour()
759
        });
760
    }
761
}
762
763
=head2 is_opened
764
765
    $calendar->is_opened($date)
766
767
Returns whether the library is open on C<$date>
768
769
=cut
770
771
sub is_opened {
772
    my($self, $date) = @_;
773
    my $branchcode = $self->{branchcode};
774
    my $schema = Koha::Database->new->schema;
775
    my $dtf = $schema->storage->datetime_parser;
776
    $date= $dtf->format_datetime($date);
777
    #if the date is not found
778
    my $is_opened = -1;
779
    my $rs = $schema->resultset('DiscreteCalendar')->search(
780
        {
781
            branchcode => $branchcode,
782
        },
783
        {
784
            where   => \['date = DATE(?)', $date]
785
        }
786
    );
787
    $is_opened = $rs->next()->is_opened() if $rs->count() != 0;
788
789
    return $is_opened;
790
}
791
792
=head2 is_holiday
793
794
    $calendar->is_holiday($date)
795
796
Returns whether C<$date> is a holiday or not
797
798
=cut
799
800
sub is_holiday {
801
    my($self, $date) = @_;
802
    my $branchcode = $self->{branchcode};
803
    my $schema = Koha::Database->new->schema;
804
    my $dtf = $schema->storage->datetime_parser;
805
    $date= $dtf->format_datetime($date);
806
    #if the date is not found
807
    my $isHoliday = -1;
808
    my $rs = $schema->resultset('DiscreteCalendar')->search(
809
        {
810
            branchcode => $branchcode,
811
        },
812
        {
813
            where   => \['date = DATE(?)', $date]
814
        }
815
    );
816
817
    if ($rs->count() != 0) {
818
        $isHoliday = ($rs->first()->is_opened() ? 0 : 1);
819
    }
820
821
    return $isHoliday;
822
}
823
824
=head2 copy_holiday
825
826
  $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
827
828
Copies a holiday's parameters from a range to a new range
829
C<$from_startDate> the source holiday's start date
830
C<$from_endDate> the source holiday's end date
831
C<$to_startDate> the destination holiday's start date
832
C<$to_endDate> the destination holiday's end date
833
C<$daysnumber> the number of days in the range.
834
835
Both ranges should have the same number of days in them.
836
837
=cut
838
839
sub copy_holiday {
840
    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
841
    my $branchcode = $self->{branchcode};
842
    my $copyFromType =  $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
843
    my $schema = Koha::Database->new->schema;
844
    my $dtf = $schema->storage->datetime_parser;
845
846
    if ($copyFromType eq 'oneDay'){
847
        my $where;
848
        $to_startDate = $dtf->format_datetime($to_startDate);
849
        if ($to_startDate && $to_endDate) {
850
            $to_endDate = $dtf->format_datetime($to_endDate);
851
            $where = { date => { -between => [$to_startDate, $to_endDate]}};
852
        } else {
853
            $where = { date => $to_startDate };
854
        }
855
856
        $from_startDate = $dtf->format_datetime($from_startDate);
857
        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
858
            {
859
                branchcode  => $branchcode,
860
                date        => $from_startDate
861
            }
862
        );
863
        my $toDates = $schema->resultset('DiscreteCalendar')->search(
864
            {
865
                branchcode  => $branchcode,
866
            },
867
            {
868
                where       => $where
869
            }
870
        );
871
872
        my $copyDate = $fromDate->next();
873
        while (my $date = $toDates->next()){
874
            $date->update({
875
                is_opened    => $copyDate->is_opened(),
876
                holiday_type => $copyDate->holiday_type(),
877
                note        => $copyDate->note(),
878
                open_hour    => $copyDate->open_hour(),
879
                close_hour   => $copyDate->close_hour()
880
            })
881
        }
882
883
    }else{
884
        my $endDate = dt_from_string($from_endDate);
885
        $to_startDate = $dtf->format_datetime($to_startDate);
886
        $to_endDate = $dtf->format_datetime($to_endDate);
887
        if($daysnumber == 7){
888
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
889
                my $formatedDate = $dtf->format_datetime($tempDate);
890
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
891
                    {
892
                        branchcode  => $branchcode,
893
                        date        => $formatedDate,
894
                    },
895
                    {
896
                        select  => [{ DAYOFWEEK => 'date' }],
897
                        as      => [qw/ weekday /],
898
                        columns =>[ qw/ holiday_type note open_hour close_hour note/]
899
                    }
900
                );
901
                my $copyDate = $fromDate->next();
902
                my $weekday = $copyDate->get_column('weekday');
903
904
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
905
                    {
906
                        branchcode  => $branchcode,
907
908
                    },
909
                    {
910
                        where       => {date => {-between => [$to_startDate, $to_endDate]}, "DAYOFWEEK(date)" => $weekday},
911
                    }
912
                );
913
                my $copyToDate = $toDate->next();
914
                $copyToDate->update({
915
                    is_opened    => $copyDate->is_opened(),
916
                    holiday_type => $copyDate->holiday_type(),
917
                    note        => $copyDate->note(),
918
                    open_hour    => $copyDate->open_hour(),
919
                    close_hour   => $copyDate->close_hour()
920
                });
921
922
            }
923
        }else{
924
            my $to_startDate = dt_from_string($to_startDate);
925
            my $to_endDate = dt_from_string($to_endDate);
926
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
927
                my $from_formatedDate = $dtf->format_datetime($tempDate);
928
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
929
                    {
930
                        branchcode  => $branchcode,
931
                        date        => $from_formatedDate,
932
                    },
933
                    {
934
                        order_by    => { -asc => 'date' }
935
                    }
936
                );
937
                my $to_formatedDate = $dtf->format_datetime($to_startDate);
938
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
939
                    {
940
                        branchcode  => $branchcode,
941
                        date        => $to_formatedDate
942
                    },
943
                    {
944
                        order_by    => { -asc => 'date' }
945
                    }
946
                );
947
                my $copyDate = $fromDate->next();
948
                $toDate->next()->update({
949
                    is_opened    => $copyDate->is_opened(),
950
                    holiday_type => $copyDate->holiday_type(),
951
                    note        => $copyDate->note(),
952
                    open_hour    => $copyDate->open_hour(),
953
                    close_hour   => $copyDate->close_hour()
954
                });
955
                $to_startDate->add(days =>1);
956
            }
957
        }
958
959
960
    }
961
}
962
963
=head2 days_between
964
965
   $cal->days_between( $start_date, $end_date )
966
967
Calculates the number of days the library is opened between C<$start_date> and C<$end_date>
968
969
=cut
970
971
sub days_between {
972
    my ($self, $start_date, $end_date, ) = @_;
973
    my $branchcode = $self->{branchcode};
974
975
    if ( $start_date->compare($end_date) > 0 ) {
976
        # swap dates
977
        ($start_date, $end_date) = ($end_date, $start_date);
978
    }
979
980
    my $schema = Koha::Database->new->schema;
981
    my $dtf = $schema->storage->datetime_parser;
982
    $start_date = $dtf->format_datetime($start_date->clone->truncate(to => 'day'));
983
    $end_date = $dtf->format_datetime($end_date->clone->truncate(to => 'day'));
984
985
    my $days_between = $schema->resultset('DiscreteCalendar')->search(
986
        {
987
            branchcode  => $branchcode,
988
            is_opened    => 1,
989
        },
990
        {
991
            where       => \['date >= date(?) AND date < date(?)',$start_date, $end_date]
992
        }
993
    );
994
995
    return DateTime::Duration->new( days => $days_between->count());
996
}
997
998
=head2 next_open_day
999
1000
   $open_date = $self->next_open_day($base_date);
1001
1002
Returns a string representing the next day the library is open, starting from C<$base_date>
1003
1004
=cut
1005
1006
sub next_open_day {
1007
    my ( $self, $date ) = @_;
1008
    my $branchcode = $self->{branchcode};
1009
    my $schema = Koha::Database->new->schema;
1010
    my $dtf = $schema->storage->datetime_parser;
1011
    $date = $dtf->format_datetime($date);
1012
1013
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1014
        {
1015
            branchcode  => $branchcode,
1016
            is_opened    => 1,
1017
        },
1018
        {
1019
            where       => \['date > date(?)', $date],
1020
            order_by    => { -asc => 'date' },
1021
            rows        => 1
1022
        }
1023
    );
1024
    return dt_from_string( $rs->next()->date(), 'iso');
1025
}
1026
1027
=head2 prev_open_day
1028
1029
   $open_date = $self->prev_open_day($base_date);
1030
1031
Returns a string representing the closest previous day the library was open, starting from C<$base_date>
1032
1033
=cut
1034
1035
sub prev_open_day {
1036
    my ( $self, $date ) = @_;
1037
    my $branchcode = $self->{branchcode};
1038
    my $schema = Koha::Database->new->schema;
1039
    my $dtf = $schema->storage->datetime_parser;
1040
    $date = $dtf->format_datetime($date);
1041
1042
    my $rs = $schema->resultset('DiscreteCalendar')->search(
1043
        {
1044
            branchcode  => $branchcode,
1045
            is_opened    => 1,
1046
        },
1047
        {
1048
            where       => \['date < date(?)', $date],
1049
            order_by    => { -desc => 'date' },
1050
            rows        => 1
1051
        }
1052
    );
1053
    return dt_from_string( $rs->next()->date(), 'iso');
1054
}
1055
1056
=head2 days_forward
1057
1058
    $fwrd_date = $calendar->days_forward($start, $count)
1059
1060
Returns the date C<$count> days in the future from C<$start>, ignoring days where the library is closed.
1061
1062
=cut
1063
1064
sub days_forward {
1065
    my $self     = shift;
1066
    my $start_dt = shift;
1067
    my $num_days = shift;
1068
1069
    return $start_dt unless $num_days > 0;
1070
1071
    my $base_dt = $start_dt->clone();
1072
1073
    while ($num_days--) {
1074
        $base_dt = $self->next_open_day($base_dt);
1075
    }
1076
1077
    return $base_dt;
1078
}
1079
1080
=head2 hours_between
1081
1082
    $hours = $calendar->hours_between($start_dt, $end_dt)
1083
1084
Returns the number of hours between C<$start_dt> and C<$end_dt>. This is the imprecise
1085
version, which simply calculates the number of day times 24. To take opening hours into account
1086
see C<open_hours_between>/
1087
1088
=cut
1089
1090
sub hours_between {
1091
    my ($self, $start_dt, $end_dt) = @_;
1092
    my $branchcode = $self->{branchcode};
1093
    my $schema = Koha::Database->new->schema;
1094
    my $dtf = $schema->storage->datetime_parser;
1095
    my $start_date = $start_dt->clone();
1096
    my $end_date = $end_dt->clone();
1097
    my $duration = $end_date->delta_ms($start_date);
1098
    $start_date->truncate( to => 'day' );
1099
    $end_date->truncate( to => 'day' );
1100
1101
    # NB this is a kludge in that it assumes all days are 24 hours
1102
    # However for hourly loans the logic should be expanded to
1103
    # take into account open/close times then it would be a duration
1104
    # of library open hours
1105
    my $skipped_days = 0;
1106
    $start_date = $dtf->format_datetime($start_date);
1107
    $end_date = $dtf->format_datetime($end_date);
1108
    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
1109
        {
1110
            branchcode  =>  $branchcode,
1111
            is_opened    => 0
1112
        },
1113
        {
1114
            where  => {date => {-between => [$start_date, $end_date]}},
1115
        }
1116
    );
1117
1118
    if ($skipped_days = $hours_between->count()) {
1119
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
1120
    }
1121
1122
    return $duration;
1123
}
1124
1125
=head2 open_hours_between
1126
1127
  $hours = $calendar->open_hours_between($start_date, $end_date)
1128
1129
Returns the number of hours between C<$start_date> and C<$end_date>, taking into
1130
account the opening hours of the library.
1131
1132
=cut
1133
1134
sub open_hours_between {
1135
    my ($self, $start_date, $end_date) = @_;
1136
    my $branchcode = $self->{branchcode};
1137
    my $schema = Koha::Database->new->schema;
1138
    my $dtf = DateTime::Format::Strptime->new(pattern => "%F %T");
1139
    $start_date = $dtf->format_datetime($start_date);
1140
    $end_date = $dtf->format_datetime($end_date);
1141
1142
    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
1143
        {
1144
            branchcode  => $branchcode,
1145
            is_opened    => 1,
1146
        },
1147
        {
1148
            select  => \['sum(time_to_sec(timediff(close_hour, open_hour)) / 3600)'],
1149
            as      => [qw /hours_between/],
1150
            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
1151
        }
1152
    );
1153
1154
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
1155
        {
1156
            branchcode  => $branchcode,
1157
        },
1158
        {
1159
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $start_date ],
1160
            rows => 1,
1161
        }
1162
    );
1163
1164
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
1165
        {
1166
            branchcode  => $branchcode,
1167
        },
1168
        {
1169
            order_by => \[ 'ABS(DATEDIFF(date, ?))', $end_date ],
1170
            rows => 1,
1171
        }
1172
    );
1173
1174
    #Capture the time portion of the date
1175
    $start_date =~ /\s(.*)/;
1176
    my $loan_date_time = $1;
1177
    $end_date =~ /\s(.*)/;
1178
    my $return_date_time = $1;
1179
1180
    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
1181
        {
1182
            branchcode  => $branchcode,
1183
            is_opened    => 1,
1184
        },
1185
        {
1186
            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()],
1187
            as      => [qw /not_used_hours/],
1188
        }
1189
    );
1190
1191
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
1192
}
1193
1194
=head2 addDate
1195
1196
  my $dt = $calendar->addDate($date, $dur, $unit)
1197
1198
C<$date> is a DateTime object representing the starting date of the interval.
1199
C<$offset> is a duration to add to it (DateTime::Duration objects are supported as legacy)
1200
C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
1201
1202
=cut
1203
1204
sub addDate {
1205
    my ( $self, $startdate, $add_duration, $unit ) = @_;
1206
1207
    # Default to days duration (legacy support I guess)
1208
    if ( ref $add_duration ne 'DateTime::Duration' ) {
1209
        $add_duration = DateTime::Duration->new( days => $add_duration );
1210
    }
1211
1212
    $unit ||= 'days'; # default days ?
1213
    my $dt;
1214
1215
    if ( $unit eq 'hours' ) {
1216
        # Fixed for legacy support. Should be set as a branch parameter
1217
        my $return_by_hour = 10;
1218
1219
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
1220
    } else {
1221
        # days
1222
        $dt = $self->addDays($startdate, $add_duration);
1223
    }
1224
1225
    return $dt;
1226
}
1227
1228
=head2 addHours
1229
1230
  $end = $calendar->addHours($start, $hours_duration, $return_by_hour)
1231
1232
Add C<$hours_duration> to C<$start> date.
1233
C<$return_by_hour> is an integer value representing the opening hour for the branch
1234
1235
=cut
1236
1237
sub addHours {
1238
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
1239
    my $base_date = $startdate->clone();
1240
1241
    $base_date->add_duration($hours_duration);
1242
1243
    # If we are using the calendar behave for now as if Datedue
1244
    # was the chosen option (current intended behaviour)
1245
1246
    if ( $self->{days_mode} ne 'Days' &&
1247
    $self->is_holiday($base_date) ) {
1248
1249
        if ( $hours_duration->is_negative() ) {
1250
            $base_date = $self->prev_open_day($base_date);
1251
        } else {
1252
            $base_date = $self->next_open_day($base_date);
1253
        }
1254
1255
        $base_date->set_hour($return_by_hour);
1256
1257
    }
1258
1259
    return $base_date;
1260
}
1261
1262
=head2 addDays
1263
1264
  $date = $calendar->addDays($start, $duration)
1265
1266
Add C<$days_duration> to C<$start> date. If the calendar's days_mode is set
1267
to 'Calendar', it ignores closed days. Else if the calendar is set to 'Datedue'
1268
it calculates the date normally, and then pushes to result to the next open day.
1269
1270
=cut
1271
1272
sub addDays {
1273
    my ( $self, $startdate, $days_duration ) = @_;
1274
    my $base_date = $startdate->clone();
1275
1276
    $self->{days_mode} ||= q{};
1277
1278
    if ( $self->{days_mode} eq 'Calendar' ) {
1279
        # use the calendar to skip all days the library is closed
1280
        # when adding
1281
        my $days = abs $days_duration->in_units('days');
1282
1283
        if ( $days_duration->is_negative() ) {
1284
            while ($days) {
1285
                $base_date = $self->prev_open_day($base_date);
1286
                --$days;
1287
            }
1288
        } else {
1289
            while ($days) {
1290
                $base_date = $self->next_open_day($base_date);
1291
                --$days;
1292
            }
1293
        }
1294
1295
    } else { # Days or Datedue
1296
        # use straight days, then use calendar to push
1297
        # the date to the next open day if Datedue
1298
        $base_date->add_duration($days_duration);
1299
1300
        if ( $self->{days_mode} eq 'Datedue' ) {
1301
            # Datedue, then use the calendar to push
1302
            # the date to the next open day if holiday
1303
            if (!$self->is_opened($base_date) ) {
1304
1305
                if ( $days_duration->is_negative() ) {
1306
                    $base_date = $self->prev_open_day($base_date);
1307
                } else {
1308
                    $base_date = $self->next_open_day($base_date);
1309
                }
1310
            }
1311
        }
1312
    }
1313
1314
    return $base_date;
1315
}
1316
1317
1;
(-)a/Koha/Hold.pm (-4 / +4 lines)
Lines 25-38 use Data::Dumper qw(Dumper); Link Here
25
25
26
use C4::Context qw(preference);
26
use C4::Context qw(preference);
27
use C4::Log;
27
use C4::Log;
28
28
use Koha::DiscreteCalendar;
29
use Koha::DateUtils qw(dt_from_string output_pref);
29
use Koha::DateUtils qw(dt_from_string output_pref);
30
use Koha::Patrons;
30
use Koha::Patrons;
31
use Koha::Biblios;
31
use Koha::Biblios;
32
use Koha::Items;
32
use Koha::Items;
33
use Koha::Libraries;
33
use Koha::Libraries;
34
use Koha::Old::Holds;
34
use Koha::Old::Holds;
35
use Koha::Calendar;
35
use Koha::DiscreteCalendar;
36
36
37
use Koha::Exceptions::Hold;
37
use Koha::Exceptions::Hold;
38
38
Lines 64-70 sub age { Link Here
64
    my $age;
64
    my $age;
65
65
66
    if ( $use_calendar ) {
66
    if ( $use_calendar ) {
67
        my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
67
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
68
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
68
        $age = $calendar->days_between( dt_from_string( $self->reservedate ), $today );
69
    }
69
    }
70
    else {
70
    else {
Lines 178-184 sub set_waiting { Link Here
178
178
179
    my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
179
    my $max_pickup_delay = C4::Context->preference("ReservesMaxPickUpDelay");
180
    my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
180
    my $cancel_on_holidays = C4::Context->preference('ExpireReservesOnHolidays');
181
    my $calendar = Koha::Calendar->new( branchcode => $self->branchcode );
181
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $self->branchcode });
182
182
183
    my $expirationdate = $today->clone;
183
    my $expirationdate = $today->clone;
184
    $expirationdate->add(days => $max_pickup_delay);
184
    $expirationdate->add(days => $max_pickup_delay);
(-)a/Koha/Schema/Result/DiscreteCalendar.pm (+111 lines)
Line 0 Link Here
1
use utf8;
2
package Koha::Schema::Result::DiscreteCalendar;
3
4
# Created by DBIx::Class::Schema::Loader
5
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6
7
=head1 NAME
8
9
Koha::Schema::Result::DiscreteCalendar
10
11
=cut
12
13
use strict;
14
use warnings;
15
16
use base 'DBIx::Class::Core';
17
18
=head1 TABLE: C<discrete_calendar>
19
20
=cut
21
22
__PACKAGE__->table("discrete_calendar");
23
24
=head1 ACCESSORS
25
26
=head2 date
27
28
  data_type: 'datetime'
29
  datetime_undef_if_invalid: 1
30
  is_nullable: 0
31
32
=head2 branchcode
33
34
  data_type: 'varchar'
35
  is_nullable: 0
36
  size: 10
37
38
=head2 is_opened
39
40
  data_type: 'tinyint'
41
  default_value: 1
42
  is_nullable: 1
43
44
=head2 holiday_type
45
46
  data_type: 'varchar'
47
  default_value: (empty string)
48
  is_nullable: 1
49
  size: 1
50
51
=head2 note
52
53
  data_type: 'varchar'
54
  default_value: (empty string)
55
  is_nullable: 1
56
  size: 30
57
58
=head2 open_hour
59
60
  data_type: 'time'
61
  is_nullable: 0
62
63
=head2 close_hour
64
65
  data_type: 'time'
66
  is_nullable: 0
67
68
=cut
69
70
__PACKAGE__->add_columns(
71
  "date",
72
  {
73
    data_type => "datetime",
74
    datetime_undef_if_invalid => 1,
75
    is_nullable => 0,
76
  },
77
  "branchcode",
78
  { data_type => "varchar", is_nullable => 0, size => 10 },
79
  "is_opened",
80
  { data_type => "tinyint", default_value => 1, is_nullable => 1 },
81
  "holiday_type",
82
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 1 },
83
  "note",
84
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 30 },
85
  "open_hour",
86
  { data_type => "time", is_nullable => 0 },
87
  "close_hour",
88
  { data_type => "time", is_nullable => 0 },
89
);
90
91
=head1 PRIMARY KEY
92
93
=over 4
94
95
=item * L</branchcode>
96
97
=item * L</date>
98
99
=back
100
101
=cut
102
103
__PACKAGE__->set_primary_key("branchcode", "date");
104
105
106
# Created by DBIx::Class::Schema::Loader v0.07045 @ 2017-04-19 10:07:41
107
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wtctW8ZzCkyCZFZmmavFEw
108
109
110
# You can replace this text with custom code or comments, and it will be preserved on regeneration
111
1;
(-)a/circ/returns.pl (-1 / +2 lines)
Lines 48-56 use C4::Reserves; Link Here
48
use C4::RotatingCollections;
48
use C4::RotatingCollections;
49
use Koha::AuthorisedValues;
49
use Koha::AuthorisedValues;
50
use Koha::BiblioFrameworks;
50
use Koha::BiblioFrameworks;
51
use Koha::Calendar;
52
use Koha::Checkouts;
51
use Koha::Checkouts;
53
use Koha::DateUtils;
52
use Koha::DateUtils;
53
use Koha::DiscreteCalendar;
54
use Koha::Holds;
54
use Koha::Holds;
55
use Koha::Items;
55
use Koha::Items;
56
use Koha::Patrons;
56
use Koha::Patrons;
Lines 205-210 my $dropboxmode = $query->param('dropboxmode'); Link Here
205
my $dotransfer  = $query->param('dotransfer');
205
my $dotransfer  = $query->param('dotransfer');
206
my $canceltransfer = $query->param('canceltransfer');
206
my $canceltransfer = $query->param('canceltransfer');
207
my $dest = $query->param('dest');
207
my $dest = $query->param('dest');
208
my $calendar    = Koha::DiscreteCalendar->new({ branchcode => $userenv_branch });
208
#dropbox: get last open day (today - 1)
209
#dropbox: get last open day (today - 1)
209
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
210
my $dropboxdate = Koha::Checkouts::calculate_dropbox_date();
210
211
(-)a/koha-tmpl/intranet-tmpl/prog/css/discretecalendar.css (+206 lines)
Line 0 Link Here
1
#jcalendar-container .ui-datepicker {
2
    font-size:185%;
3
}
4
5
#holidayweeklyrepeatable,
6
#holidaysyearlyrepeatable,
7
#holidaysunique,
8
#holidayexceptions {
9
    font-size:90%;
10
    margin-bottom:1em;
11
}
12
13
#showHoliday {
14
    margin:.5em 0;
15
}
16
17
.key {
18
    padding:3px;
19
    white-space:nowrap;
20
    line-height:230%;
21
}
22
23
.ui-datepicker {
24
    font-size:150%;
25
}
26
27
.ui-datepicker th,
28
.ui-datepicker .ui-datepicker-title select {
29
    font-size:80%;
30
}
31
32
.ui-datepicker td a {
33
    padding:.5em;
34
}
35
36
.ui-datepicker td span {
37
    padding:.5em;
38
    border:1px solid #BCBCBC;
39
}
40
41
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
42
    font-size:80%;
43
}
44
45
.key {
46
    padding:3px;
47
    white-space:nowrap;
48
    line-height:230%;
49
}
50
51
.normalday {
52
    background-color:#EDEDED;
53
    color:#000;
54
    border:1px solid #BCBCBC;
55
}
56
57
.ui-datepicker-unselectable {
58
    padding:.5em;
59
    white-space:nowrap;
60
}
61
62
.ui-state-disabled {
63
    padding:.5em;
64
    white-space:nowrap;
65
}
66
67
.exception {
68
    background-color:#b3d4ff;
69
    color:#000;
70
    border:1px solid #BCBCBC;
71
}
72
73
.past-date {
74
    background-color:#e6e6e6;
75
    color:#555;
76
    border:1px solid #BCBCBC;
77
}
78
79
td.past-date a.ui-state-default {
80
    background:#e6e6e6;
81
    color:#555;
82
}
83
84
.float {
85
    background-color:#6f3;
86
    color:#000;
87
    border:1px solid #BCBCBC;
88
}
89
90
.holiday {
91
    background-color:#ffaeae;
92
    color:#000;
93
    border:1px solid #BCBCBC;
94
}
95
96
.repeatableweekly {
97
    background-color:#FF9;
98
    color:#000;
99
    border:1px solid #BCBCBC;
100
}
101
102
.repeatableyearly {
103
    background-color:#FC6;
104
    color:#000;
105
    border:1px solid #BCBCBC;
106
}
107
108
td.exception a.ui-state-default {
109
    background:#b3d4ff none;
110
    color:#000;
111
    border:1px solid #BCBCBC;
112
}
113
114
td.float a.ui-state-default {
115
    background:#6f3 none;
116
    color:#000;
117
    border:1px solid #BCBCBC;
118
}
119
120
td.holiday a.ui-state-default {
121
    background:#ffaeae none;
122
    color:#000;
123
    border:1px solid #BCBCBC;
124
}
125
126
td.repeatableweekly a.ui-state-default {
127
    background:#FF9 none;
128
    color:#000;
129
    border:1px solid #BCBCBC;
130
}
131
132
td.repeatableyearly a.ui-state-default {
133
    background:#FC6 none;
134
    color:#000;
135
    border:1px solid #BCBCBC;
136
}
137
138
.information {
139
    background-color:#DCD2F1;
140
    width:300px;
141
    display:none;
142
    border:1px solid #000;
143
    color:#000;
144
    font-size:8pt;
145
    font-weight:700;
146
    background-color:#FFD700;
147
    cursor:pointer;
148
    padding:2px;
149
}
150
151
.panel {
152
    z-index:1;
153
    display:none;
154
    border:3px solid #CCC;
155
    padding:3px;
156
    margin-top:.3em;
157
    background-color:#FEFEFE;
158
}
159
160
fieldset.brief {
161
    border:0;
162
    margin-top:0;
163
}
164
165
h1 select {
166
    width:20em;
167
}
168
169
div.yui-b fieldset.brief ol {
170
    font-size:100%;
171
}
172
173
div.yui-b fieldset.brief li,
174
div.yui-b fieldset.brief li.radio {
175
    padding:.2em 0;
176
}
177
178
.help {
179
    margin:.3em 0;
180
    border:1px solid #EEE;
181
    padding:.3em .7em;
182
    font-size:90%;
183
}
184
185
.calendar td,
186
.calendar th,
187
.calendar .button,
188
.calendar tbody .day {
189
    padding:.7em;
190
    font-size:110%;
191
}
192
193
.calendar {
194
    width:auto;
195
    border:0;
196
}
197
198
.copyHoliday form li {
199
    display:table-row;
200
}
201
202
.copyHoliday form li b,
203
.copyHoliday form li input {
204
    display:table-cell;
205
    margin-bottom:2px;
206
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc (-1 / +1 lines)
Lines 104-110 Link Here
104
<h5>Additional tools</h5>
104
<h5>Additional tools</h5>
105
<ul>
105
<ul>
106
    [% IF ( CAN_user_tools_edit_calendar ) %]
106
    [% IF ( CAN_user_tools_edit_calendar ) %]
107
	<li><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></li>
107
    <li><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></li>
108
    [% END %]
108
    [% END %]
109
    [% IF ( CAN_user_tools_manage_csv_profiles ) %]
109
    [% IF ( CAN_user_tools_manage_csv_profiles ) %]
110
	<li><a href="/cgi-bin/koha/tools/csv-profiles.pl">CSV profiles</a></li>
110
	<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 (+678 lines)
Line 0 Link Here
1
[% USE Asset %]
2
[% USE Branches %]
3
[% SET footerjs = 1 %]
4
[% INCLUDE 'doc-head-open.inc' %]
5
<title>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) %] calendar</title>
6
[% INCLUDE 'doc-head-close.inc' %]
7
[% Asset.css("css/datatables.css") %]
8
[% Asset.css("css/discretecalendar.css") %]
9
</head>
10
11
<body id="tools_holidays" class="tools">
12
[% INCLUDE 'header.inc' %]
13
[% INCLUDE 'cat-search.inc' %]
14
15
<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo; [% Branches.GetName( branch ) %] calendar</div>
16
17
<div id="doc3" class="yui-t1">
18
19
   <div id="bd">
20
    <div id="yui-main">
21
    <div class="yui-b">
22
    <h2>[% Branches.GetName( branch ) %] calendar</h2>
23
    <div class="yui-g">
24
    <div class="yui-u first" style="width:60%">
25
        <label for="branch">Define the holidays for:</label>
26
        <form method="post" onsubmit="return validateForm('CopyCalendar')">
27
            <select id="branch" name="branch">
28
                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
29
            </select>
30
            Copy calendar to
31
            <select id='newBranch' name ='newBranch'>
32
                <option value=""></option>
33
                [% FOREACH l IN Branches.all() %]
34
                    [% UNLESS branch == l.branchcode %]
35
                    <option value="[% l.branchcode %]">[% l.branchname %]</option>
36
                    [% END %]
37
                [% END %]
38
            </select>
39
            <input type="hidden" name="action" value="copyBranch" />
40
            <input type="submit" value="Clone">
41
        </form>
42
            <h3>Calendar information</h3>
43
            <div id="jcalendar-container" style="float: left"></div>
44
    <!-- ***************************** Panel to deal with new holidays **********************  -->
45
    [% UNLESS  datesInfos %]
46
    <div class="alert alert-danger" style="float: left; margin-left:15px">
47
        <strong>Error!</strong> You have to run generate_discrete_calendar.pl in order to use Discrete Calendar.
48
    </div>
49
    [% END %]
50
51
    [% IF  no_branch_selected %]
52
    <div class="alert alert-danger" style="float: left; margin-left:15px">
53
        <strong>No library set!</strong> You are using the default calendar.
54
    </div>
55
    [% END %]
56
57
    <div class="panel newHoliday" id="newHoliday" style="float: left; margin-left:15px">
58
        <form method="post" onsubmit="return validateForm('newHoliday')">
59
            <fieldset class="brief">
60
                <h3>Edit date details</h3>
61
                <span id="holtype"></span>
62
                <ol>
63
                    <li>
64
                        <strong>Library:</strong>
65
                        <span id="newBranchNameOutput"></span>
66
                        <input type="hidden" id="branch" name="branch" />
67
                    </li>
68
                    <li>
69
                        <strong>From date:</strong>
70
                        <span id="newDaynameOutput"></span>,
71
72
                        [% 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 %]
73
74
                        <input type="hidden" id="newDayname" name="showDayname" />
75
                        <input type="hidden" id="Day" name="Day" />
76
                        <input type="hidden" id="Month" name="Month" />
77
                        <input type="hidden" id="Year" name="Year" />
78
                    </li>
79
                    <li class="dateinsert">
80
                        <b>To date: </b>
81
                        <input type="text" id="from_copyToDatePicker" name="toDate" size="20" class="datepicker" />
82
                    </li>
83
                    <li>
84
                        <label for="title">Title: </label><input type="text" name="Title" id="title" size="35" />
85
                    </li>
86
                    <li id="holidayType">
87
                        <label for="holidayType">Date type</label>
88
                        <select name ='holidayType'>
89
                            <option value="empty"></option>
90
                            <option value="none">Working day</option>
91
                            <option value="E">Unique holiday</option>
92
                            <option value="W">Weekly holiday</option>
93
                            <option value="R">Repeatable holiday</option>
94
                            <option value="F">Floating holiday</option>
95
                            <option value="N" disabled>Need validation</option>
96
                        </select>
97
                    </li>
98
                    <li id="days_of_week" style="display :none">
99
                        <label for="day_of_week">Week day</label>
100
                        <select name ='day_of_week'>
101
                            <option value="everyday">Everyday</option>
102
                            <option value="1">Sundays</option>
103
                            <option value="2">Mondays</option>
104
                            <option value="3">Tuesdays</option>
105
                            <option value="4">Wednesdays</option>
106
                            <option value="5">Thursdays</option>
107
                            <option value="6">Fridays</option>
108
                            <option value="7">Saturdays</option>
109
                        </select>
110
                    </li>
111
                    <li class="radio" id="deleteType" style="display : none;" >
112
                        <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label>
113
                        <a href="#" class="helptext">[?]</a>
114
                        <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if working day is selected.</div>
115
                    </li>
116
                    <li>
117
                        <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' style="display :flex"  >
118
                    </li>
119
                    <li>
120
                        <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' style="display :flex" >
121
                    </li>
122
                    <li class="radio">
123
                        <input type="radio" name="action" id="EditRadioButton" value="edit" checked/>
124
                        <label for="EditRadioButton">Edit selected dates</label>
125
                    </li>
126
                    <li class="radio">
127
                        <input type="radio" name="action" id="CopyRadioButton" value="copyDates" />
128
                        <label for="CopyRadioButton">Copy to different dates</label>
129
                        <div class="CopyDatePanel" style="display:none; padding-left:15px">
130
                            <b>From : </b>
131
                            <input type="text" id="to_copyFromDatePicker" size="20" class="datepicker"/>
132
                            <b>To : </b>
133
                            <input type="text" id="to_copyToDatePicker" size="20" class="datepicker"/>
134
                        </div>
135
                        <input type="hidden" name="daysnumber" id='daysnumber'>
136
                        <!-- These  yyyy-mm-dd -->
137
                        <input type="hidden" name="from_copyFrom" id='from_copyFrom'>
138
                        <input type="hidden" name="from_copyTo" id='from_copyTo'>
139
                        <input type="hidden" name="to_copyFrom" id='to_copyFrom'>
140
                        <input type="hidden" name="to_copyTo" id='to_copyTo'>
141
                        <input type="hidden" name="local_today" id='local_today'>
142
                    </li>
143
                </ol>
144
                <fieldset class="action">
145
                    <input type="submit" name="submit" value="Save" />
146
                    <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
147
                </fieldset>
148
            </fieldset>
149
        </form>
150
    </div>
151
152
<!-- ************************************************************************************** -->
153
<!-- ******                              MAIN SCREEN CODE                            ****** -->
154
<!-- ************************************************************************************** -->
155
156
</div>
157
<div class="yui-u" style="width : 40%">
158
    <div class="help">
159
        <h4>Hints</h4>
160
        <ul>
161
            <li>Search in the calendar the day you want to set as holiday.</li>
162
            <li>Click the date to add or edit a holiday.</li>
163
            <li>Specify how the holiday should repeat.</li>
164
            <li>Click Save to finish.</li>
165
            <li>PS:
166
                <ul>
167
                    <li>Past dates cannot be changed</li>
168
                    <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li>
169
                </ul>
170
            </li>
171
        </ul>
172
        <h4>Key</h4>
173
        <p>
174
            <span class="key normalday">Working day </span>
175
            <span class="key holiday">Unique holiday</span>
176
            <span class="key repeatableweekly">Holiday repeating weekly</span>
177
            <span class="key repeatableyearly">Holiday repeating yearly</span>
178
            <span class="key float">Floating holiday</span>
179
            <span class="key exception">Need validation</span>
180
        </p>
181
    </div>
182
<div id="holiday-list">
183
184
    [% IF ( NEED_VALIDATION_HOLIDAYS ) %]
185
    <h3>Need validation holidays</h3>
186
    <table id="holidaysunique">
187
        <thead>
188
            <tr>
189
                <th class="exception">Date</th>
190
                <th class="exception">Title</th>
191
            </tr>
192
        </thead>
193
        <tbody>
194
            [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %]
195
            <tr>
196
                <td><a href="#doc3" onclick="go_to_date('[% need_validation_holiday.date %]')"><span title="[% need_validation_holiday.DATE_SORT %]">[% need_validation_holiday.outputdate %]</span></a></td>
197
                <td>[% need_validation_holiday.note %]</td>
198
            </tr>
199
            [% END %]
200
        </tbody>
201
    </table>
202
    [% END %]
203
204
    [% IF ( WEEKLY_HOLIDAYS ) %]
205
    <h3>Weekly - Repeatable holidays</h3>
206
    <table id="holidayweeklyrepeatable">
207
        <thead>
208
            <tr>
209
                <th class="repeatableweekly">Day of week</th>
210
                <th class="repeatableweekly">Title</th>
211
            </tr>
212
        </thead>
213
        <tbody>
214
            [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %]
215
            <tr>
216
                <td>[% WEEK_DAYS_LOO.weekday %]</td>
217
            </td>
218
            <td>[% WEEK_DAYS_LOO.note %]</td>
219
        </tr>
220
        [% END %]
221
    </tbody>
222
</table>
223
[% END %]
224
225
[% IF ( REPEATABLE_HOLIDAYS ) %]
226
<h3>Yearly - Repeatable holidays</h3>
227
<table id="holidaysyearlyrepeatable">
228
    <thead>
229
        <tr>
230
            [% IF ( dateformat == "metric" ) %]
231
            <th class="repeatableyearly">Day/month</th>
232
            [% ELSE %]
233
            <th class="repeatableyearly">Month/day</th>
234
            [% END %]
235
            <th class="repeatableyearly">Title</th>
236
        </tr>
237
    </thead>
238
    <tbody>
239
        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %]
240
        <tr>
241
            [% IF ( dateformat == "metric" ) %]
242
            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.day %]/[% DAY_MONTH_HOLIDAYS_LOO.month %]</span></td>
243
            [% ELSE %]
244
            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.month %]/[% DAY_MONTH_HOLIDAYS_LOO.day %]</span></td>
245
            [% END %]
246
            <td>[% DAY_MONTH_HOLIDAYS_LOO.note %]</td>
247
        </tr>
248
        [% END %]
249
    </tbody>
250
</table>
251
[% END %]
252
253
[% IF ( UNIQUE_HOLIDAYS ) %]
254
<h3>Unique holidays</h3>
255
<table id="holidaysunique">
256
    <thead>
257
        <tr>
258
            <th class="holiday">Date</th>
259
            <th class="holiday">Title</th>
260
        </tr>
261
    </thead>
262
    <tbody>
263
        [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %]
264
        <tr>
265
            <td><a href="#doc3" onclick="go_to_date('[% HOLIDAYS_LOO.date %]')"><span title="[% HOLIDAYS_LOO.DATE_SORT %]">[% HOLIDAYS_LOO.outputdate %]</span></a></td>
266
            <td>[% HOLIDAYS_LOO.note %]</td>
267
        </tr>
268
        [% END %]
269
    </tbody>
270
</table>
271
[% END %]
272
273
[% IF ( FLOAT_HOLIDAYS ) %]
274
<h3>Floating holidays</h3>
275
<table id="holidaysunique">
276
    <thead>
277
        <tr>
278
            <th class="float">Date</th>
279
            <th class="float">Title</th>
280
        </tr>
281
    </thead>
282
    <tbody>
283
        [% FOREACH float_holiday IN FLOAT_HOLIDAYS %]
284
        <tr>
285
            <td><a href="#doc3" onclick="go_to_date('[% float_holiday.date %]')"><span title="[% float_holiday.DATE_SORT %]">[% float_holiday.outputdate %]</span></a></td>
286
            <td>[% float_holiday.note %]</td>
287
        </tr>
288
        [% END %]
289
    </tbody>
290
</table>
291
[% END %]
292
</div>
293
</div>
294
</div>
295
</div>
296
</div>
297
298
<div class="yui-b noprint">
299
[% INCLUDE 'tools-menu.inc' %]
300
</div>
301
</div>
302
[% MACRO jsinclude BLOCK %]
303
[% Asset.js("lib/jquery/plugins/jquery-ui-timepicker-addon.min.js") %]
304
[% INCLUDE 'calendar.inc' %]
305
[% INCLUDE 'datatables.inc' %]
306
<script type="text/javascript">
307
    //<![CDATA[
308
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
309
        // Array containing all the information about each date in the calendar.
310
        var datesInfos = new Array();
311
        [% FOREACH date IN datesInfos %]
312
            datesInfos["[% date.date %]"] = {
313
                title : "[% date.note %]",
314
                outputdate : "[% date.outputdate %]",
315
                holiday_type:"[% date.holiday_type %]",
316
                open_hour: "[% date.open_hour %]",
317
                close_hour: "[% date.close_hour %]"
318
            };
319
        [% END %]
320
321
        /**
322
        * Displays the details of the selected date on a side panel
323
        */
324
        function showHoliday (date_obj, dateString, dayName, day, month, year, weekDay, title, holidayType) {
325
            $("#newHoliday").slideDown("fast");
326
            $("#copyHoliday").slideUp("fast");
327
            $('#newDaynameOutput').html(dayName);
328
            $('#newDayname').val(dayName);
329
            $('#newBranchNameOutput').html($("#branch :selected").text());
330
            $(".newHoliday ,#branch").val($('#branch').val());
331
            $('#newDayOutput').html(day);
332
            $(".newHoliday #Day").val(day);
333
            $(".newHoliday #Month").val(month);
334
            $(".newHoliday #Year").val(year);
335
            $("#newMonthOutput").html(month);
336
            $("#newYearOutput").html(year);
337
            $(".newHoliday, #Weekday").val(weekDay);
338
339
            $('.newHoliday #title').val(title);
340
            $('#HolidayType').val(holidayType);
341
            $('#days_of_week option[value="'+ (weekDay + 1)  +'"]').attr('selected', true);
342
            $('#openHour').val(datesInfos[dateString].open_hour);
343
            $('#closeHour').val(datesInfos[dateString].close_hour);
344
            $('#local_today').val(getSeparetedDate(new Date()).dateString);
345
346
            //This changes the label of the date type on the edit panel
347
            if(holidayType == 'W') {
348
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
349
            } else if(holidayType == 'R') {
350
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
351
            } else if(holidayType == 'F') {
352
                $("#holtype").attr("class","key float").html(_("Floating holiday"));
353
            } else if(holidayType == 'N') {
354
                $("#holtype").attr("class","key exception").html(_("Needs validation"));
355
            } else if(holidayType == 'E') {
356
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
357
            } else{
358
                $("#holtype").attr("class","key normalday").html(_("Working day "));
359
            }
360
361
            //Select the correct holiday type on the dropdown menu
362
            if (datesInfos[dateString].holiday_type !=''){
363
                var type = datesInfos[dateString].holiday_type;
364
                $('#holidayType option[value="'+ type +'"]').attr('selected', true)
365
            }else{
366
                $('#holidayType option[value="none"]').attr('selected', true)
367
            }
368
369
            //If it is a weekly or repeatable holiday show the option to delete the type
370
            if(datesInfos[dateString].holiday_type == 'W' || datesInfos[dateString].holiday_type == 'R'){
371
                $('#deleteType').show("fast");
372
            }else{
373
                $('#deleteType').hide("fast");
374
            }
375
376
            //This value is to disable and hide input when the date is in the past, because you can't edit it.
377
            var value = false;
378
            var today = new Date();
379
            today.setHours(0,0,0,0);
380
            if(date_obj < today ){
381
                $("#holtype").attr("class","key past-date").html(_("Past date"));
382
                $("#CopyRadioButton").attr("checked", "checked");
383
                value = true;
384
                $(".CopyDatePanel").toggle(value);
385
            }
386
            $("#title").prop('disabled', value);
387
            $("#holidayType select").prop('disabled', value);
388
            $("#openHour").prop('disabled', value);
389
            $("#closeHour").prop('disabled', value);
390
            $("#EditRadioButton").parent().toggle(!value);
391
392
        }
393
394
        function hidePanel(aPanelName) {
395
            $("#"+aPanelName).slideUp("fast");
396
        }
397
398
        function changeBranch () {
399
            var branch = $("#branch option:selected").val();
400
            location.href='/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate %]";
401
        }
402
403
        function Help() {
404
            newin=window.open("/cgi-bin/koha/help.pl","KohaHelp",'width=600,height=600,toolbar=false,scrollbars=yes');
405
        }
406
407
        // This function gives css clases to each kind of day
408
        function dateStatusHandler(date) {
409
            date = getSeparetedDate(date);
410
            var day = date.day;
411
            var month = date.month;
412
            var year = date.year;
413
            var weekDay = date.weekDay;
414
            var dayName = weekdays[weekDay];
415
            var dateString = date.dateString;
416
            var today = new Date();
417
            today.setHours(0,0,0,0);
418
419
            if (datesInfos[dateString] && datesInfos[dateString].holiday_type =='W'){
420
                return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)];
421
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'R') {
422
                return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)];
423
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'N') {
424
                return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)];
425
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'F') {
426
                return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)];
427
            } else if (datesInfos[dateString] && datesInfos[dateString].holiday_type == 'E') {
428
                return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)];
429
            } else {
430
                if(date.date_obj < today ){
431
                    return [true, "past-date", _("Past day")];
432
                }else{
433
                    return [true, "normalday", _("Normal day")];
434
                }
435
            }
436
        }
437
438
        /* This function is in charge of showing the correct panel considering the kind of holiday */
439
        function dateChanged(text, date) {
440
            date = getSeparetedDate(date);
441
            var day = date.day;
442
            var month = date.month;
443
            var year = date.year;
444
            var weekDay = date.weekDay;
445
            var dayName = weekdays[weekDay];
446
            var dateString = date.dateString;
447
            var date_obj = date.date_obj;
448
            //set value of form hidden field
449
            $('#from_copyFrom').val(text);
450
451
            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holiday_type);
452
        }
453
454
        /**
455
        * This function separate a given date object a 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
        * Valide the forms before send them to the backend
479
        */
480
        function validateForm(form){
481
            if(form =='newHoliday' && $('#CopyRadioButton').is(':checked')){
482
                if($('#to_copyFromDatePicker').val() =='' || $('#to_copyToDatePicker').val() ==''){
483
                    alert("You have to pick a FROM and TO in the Copy to different dates.");
484
                    return false;
485
                }else if ($('#from_copyToDatePicker').val()){
486
                    var from_DateFrom = new Date($("#jcalendar-container").datepicker("getDate"));
487
                    var from_DateTo = new Date($('#from_copyToDatePicker').datepicker("getDate"));
488
                    var to_DateFrom = new Date($('#to_copyFromDatePicker').datepicker("getDate"));
489
                    var to_DateTo = new Date($('#to_copyToDatePicker').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'){
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
            $('#toDate').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
            $("#branch").change(function(){
581
                changeBranch();
582
            });
583
            $("#holidayweeklyrepeatable>tbody>tr").each(function(){
584
                var first_td = $(this).find('td').first();
585
                first_td.html(weekdays[first_td.html()]);
586
            });
587
            $("a.helptext").click(function(){
588
                $(this).parent().find(".hint").toggle(); return false;
589
            });
590
            //Set the correct coloring, default date and the date ranges for all datepickers
591
            $.datepicker.setDefaults({
592
                beforeShowDay: function(thedate) {
593
                    return dateStatusHandler(thedate);
594
                },
595
                defaultDate: new Date("[% keydate %]"),
596
                minDate: new Date("[% minDate %]"),
597
                maxDate: new Date("[% maxDate %]"),
598
                dateFormat: "yy-mm-dd"
599
            });
600
            //Main datepicker
601
            $("#jcalendar-container").datepicker({
602
                onSelect: function(dateText, inst) {
603
                    dateChanged(dateText, $(this).datepicker("getDate"));
604
                },
605
            });
606
            $('#from_copyToDatePicker').datepicker();
607
            $("#from_copyToDatePicker").change(function(){
608
                checkRange($(this).datepicker("getDate"));
609
                $('#from_copyTo').val(($(this).val()));
610
                if($('#from_copyToDatePicker').val()){
611
                    $('#days_of_week').show("fast");
612
                }else{
613
                    $('#days_of_week').hide("fast");
614
                }
615
            });
616
            //Datepickers for copy dates feature
617
            $('#to_copyFromDatePicker').datepicker();
618
            $("#to_copyFromDatePicker").change(function(){
619
                $('#to_copyFrom').val(($(this).val()));
620
            });
621
            $('#to_copyToDatePicker').datepicker();
622
            $("#to_copyToDatePicker").change(function(){
623
                $('#to_copyTo').val(($(this).val()));
624
            });
625
            //Timepickers for open and close hours
626
            $('#openHour').timepicker({
627
                showOn : 'focus',
628
                timeFormat: 'HH:mm:ss',
629
                showSecond: false,
630
                stepMinute: 5,
631
            });
632
            $('#closeHour').timepicker({
633
                showOn : 'focus',
634
                timeFormat: 'HH:mm:ss',
635
                showSecond: false,
636
                stepMinute: 5,
637
            });
638
639
            $('.newHoliday input[type="radio"]').click(function () {
640
                if ($(this).attr("id") == "CopyRadioButton") {
641
                    $(".CopyToBranchPanel").hide('fast');
642
                    $(".CopyDatePanel").show('fast');
643
                } else if ($(this).attr("id") == "CopyToBranchRadioButton"){
644
                    $(".CopyDatePanel").hide('fast');
645
                    $(".CopyToBranchPanel").show('fast');
646
                } else{
647
                    $(".CopyDatePanel").hide('fast');
648
                    $(".CopyToBranchPanel").hide('fast');
649
                }
650
            });
651
652
            $(".hidePanel").on("click",function(){
653
                if( $(this).hasClass("showHoliday") ){
654
                    hidePanel("showHoliday");
655
                }if ($(this).hasClass('newHoliday')) {
656
                    hidePanel("newHoliday");
657
                }else {
658
                    hidePanel("copyHoliday");
659
                }
660
            });
661
662
            $("#deleteType_checkbox").on("change", function(){
663
                if($("#deleteType_checkbox").is(':checked')){
664
                    $('#holidayType option[value="none"]').attr('selected', true);
665
                }
666
            });
667
            $("#holidayType select").on("change", function(){
668
                if($("#holidayType select").val() == "R"){
669
                    $('#days_of_week').hide("fast");
670
                }else if ($('#from_copyToDatePicker').val()){
671
                    $('#days_of_week').show("fast");
672
                }
673
            });
674
        });
675
    //]]>
676
</script>
677
[% END %]
678
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt (-1 / +1 lines)
Lines 85-91 Link Here
85
[% END %]
85
[% END %]
86
<dl>
86
<dl>
87
    [% IF ( CAN_user_tools_edit_calendar ) %]
87
    [% IF ( CAN_user_tools_edit_calendar ) %]
88
    <dt><a href="/cgi-bin/koha/tools/holidays.pl">Calendar</a></dt>
88
    <dt><a href="/cgi-bin/koha/tools/discrete_calendar.pl">Calendar</a></dt>
89
    <dd>Define days when the library is closed</dd>
89
    <dd>Define days when the library is closed</dd>
90
    [% END %]
90
    [% END %]
91
91
(-)a/misc/cronjobs/add_days_discrete_calendar.pl (+128 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
15
# Options
16
my $help = 0;
17
my $daysInFuture = 1;
18
my $debug = 0;
19
GetOptions (
20
    'help|?|h'  => \$help,
21
    'n=i'       => \$daysInFuture,
22
    'd|?|debug' => \$debug);
23
24
my $usage = << 'ENDUSAGE';
25
26
This script adds days into discrete_calendar table based on the same day from the week before.
27
28
Examples :
29
    The latest date on discrete_calendar is : 28-07-2017
30
    The current date : 01-08-2016
31
    The dates that will be added are : 29-07-2017, 30-07-2017, 31-07-2017, 01-08-2017
32
Open close exemples :
33
    Date added is : 29-07-2017
34
    Opening/closing hours will be base on : 22-07-2017 (- 7 days)
35
    Library open or closed will be based on : 29-07-2017 (- 1 year)
36
This script has the following parameters:
37
    -h --help: this message
38
    -n : number of days to add in the futre, default : 1
39
    -d --debug: displays all added days and errors if there is any
40
41
ENDUSAGE
42
43
if ($help) {
44
    print $usage;
45
    exit;
46
}
47
48
my $schema = Koha::Database->new->schema;
49
$schema->storage->txn_begin;
50
my $dbh = C4::Context->dbh;
51
52
# Predeclaring variables that will be used several times in the code
53
my $query;
54
my $statement;
55
56
#getting the all the branches
57
$query = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode';
58
$statement = $dbh->prepare($query);
59
$statement->execute();
60
my @branches = ();
61
for my $branchcode ($statement->fetchrow_array ) {
62
    push @branches,$branchcode;
63
}
64
65
#get the latest date in the table
66
$query = "SELECT MAX(date) FROM discrete_calendar";
67
$statement = $dbh->prepare($query);
68
$statement->execute();
69
my $latestedDate = $statement->fetchrow_array;
70
my $parser = DateTime::Format::Strptime->new(
71
    pattern => '%Y-%m-%d %H:%M:%S',
72
    on_error => 'croak',
73
);
74
75
$latestedDate = $parser->parse_datetime($latestedDate);
76
77
my $newDay = $latestedDate->clone();
78
$latestedDate->add(days => $daysInFuture);
79
80
for ($newDay->add(days => 1); $newDay <= $latestedDate; $newDay->add(days => 1)) {
81
    my $lastWeekDay = $newDay->clone();
82
    $lastWeekDay->add(days=> -8);
83
    my $dayOfWeek = $lastWeekDay->day_of_week;
84
    # Representation fix
85
    # DateTime object dow (1-7) where Monday is 1
86
    # Arrays are 0-based where 0 = Sunday, not 7.
87
    $dayOfWeek -= 1 unless $dayOfWeek == 7;
88
    $dayOfWeek = 0 if $dayOfWeek == 7;
89
90
    #checking if it was open on the same day from last year
91
    my $yearAgo = $newDay->clone();
92
    $yearAgo = $yearAgo->add(years => -1);
93
    my $last_year = 'SELECT is_opened, holiday_type, note FROM discrete_calendar WHERE date=? AND branchcode=?';
94
    my $day_last_week = "SELECT open_hour, close_hour FROM discrete_calendar WHERE DAYOFWEEK(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1";
95
    my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,open_hour,close_hour) VALUES (?,?,?,?,?)';
96
    my $note ='';
97
    #insert into discrete_calendar for each branch
98
    foreach my $branchCode(@branches){
99
        $statement = $dbh->prepare($last_year);
100
        $statement->execute($yearAgo,$branchCode);
101
        my ($is_opened, $holiday_type, $note) = $statement->fetchrow_array;
102
        #weekly and unique holidays are not replicated in the future
103
        if ($holiday_type && $holiday_type ne "R"){
104
            $is_opened = 1;
105
            if ($holiday_type eq "W" || $holiday_type eq "E"){
106
                $holiday_type='';
107
                $note='';
108
            }elsif ($holiday_type eq "F"){
109
                $holiday_type = 'N';
110
            }
111
        }
112
        $holiday_type = '' if $is_opened;
113
        $statement = $dbh->prepare($day_last_week);
114
        $statement->execute($newDay, $newDay);
115
        my ($open_hour,$close_hour ) = $statement->fetchrow_array;
116
        my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,is_opened,holiday_type, note,open_hour,close_hour) VALUES (?,?,?,?,?,?,?)';
117
        $statement = $dbh->prepare($add_Day);
118
        $statement->execute($newDay,$branchCode,$is_opened,$holiday_type,$note, $open_hour,$close_hour);
119
120
        if($debug && !$@){
121
            warn "Added day $newDay to $branchCode is opened : $is_opened, holiday_type : $holiday_type, note: $note, open_hour : $open_hour, close_hour : $close_hour \n";
122
        }elsif($@){
123
            warn "Failed to add day $newDay to $branchCode : $_\n";
124
        }
125
    }
126
}
127
# If everything went well we commit to the database
128
$schema->storage->txn_commit;
(-)a/misc/cronjobs/fines.pl (-2 / +2 lines)
Lines 37-43 use Getopt::Long; Link Here
37
use Carp;
37
use Carp;
38
use File::Spec;
38
use File::Spec;
39
39
40
use Koha::Calendar;
40
use Koha::DiscreteCalendar;
41
use Koha::DateUtils;
41
use Koha::DateUtils;
42
use C4::Log;
42
use C4::Log;
43
43
Lines 175-181 EOM Link Here
175
sub set_holiday {
175
sub set_holiday {
176
    my ( $branch, $dt ) = @_;
176
    my ( $branch, $dt ) = @_;
177
177
178
    my $calendar = Koha::Calendar->new( branchcode => $branch );
178
    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branch });
179
    return $calendar->is_holiday($dt);
179
    return $calendar->is_holiday($dt);
180
}
180
}
181
181
(-)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 444-452 elsif ( defined $text_filename ) { Link Here
444
}
444
}
445
445
446
foreach my $branchcode (@branches) {
446
foreach my $branchcode (@branches) {
447
    my $calendar;
448
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
447
    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
449
        $calendar = Koha::Calendar->new( branchcode => $branchcode );
448
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
450
        if ( $calendar->is_holiday($date_to_run) ) {
449
        if ( $calendar->is_holiday($date_to_run) ) {
451
            next;
450
            next;
452
        }
451
        }
Lines 547-559 END_SQL Link Here
547
                my $days_between;
546
                my $days_between;
548
                if ( C4::Context->preference('OverdueNoticeCalendar') )
547
                if ( C4::Context->preference('OverdueNoticeCalendar') )
549
                {
548
                {
550
                    $days_between =
549
                    my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
551
                      $calendar->days_between( dt_from_string($data->{date_due}),
550
                    $days_between = $calendar->days_between( dt_from_string($data->{date_due}), $date_to_run );
552
                        $date_to_run );
553
                }
551
                }
554
                else {
552
                else {
555
                    $days_between =
553
                    $days_between = $date_to_run->delta_days( dt_from_string($data->{date_due}) );
556
                      $date_to_run->delta_days( dt_from_string($data->{date_due}) );
557
                }
554
                }
558
                $days_between = $days_between->in_units('days');
555
                $days_between = $days_between->in_units('days');
559
                if ($triggered) {
556
                if ($triggered) {
Lines 630-638 END_SQL Link Here
630
                my $exceededPrintNoticesMaxLines = 0;
627
                my $exceededPrintNoticesMaxLines = 0;
631
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
628
                while ( my $item_info = $sth2->fetchrow_hashref() ) {
632
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
629
                    if ( C4::Context->preference('OverdueNoticeCalendar') ) {
633
                        $days_between =
630
                        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $branchcode });
634
                          $calendar->days_between(
631
                        $days_between = $calendar->days_between( dt_from_string( $item_info->{date_due} ), $date_to_run );
635
                            dt_from_string( $item_info->{date_due} ), $date_to_run );
636
                    }
632
                    }
637
                    else {
633
                    else {
638
                        $days_between =
634
                        $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
42
Lines 302-308 sub GetWaitingHolds { Link Here
302
    $sth->execute();
302
    $sth->execute();
303
    my @results;
303
    my @results;
304
    while ( my $issue = $sth->fetchrow_hashref() ) {
304
    while ( my $issue = $sth->fetchrow_hashref() ) {
305
        my $calendar = Koha::Calendar->new( branchcode => $issue->{'site'} );
305
        my $calendar = Koha::DiscreteCalendar->new({ branchcode => $issue->{'site'} });
306
306
307
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
307
        my $waiting_date = dt_from_string( $issue->{waitingdate}, 'sql' );
308
        my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
308
        my $pickup_date = $waiting_date->clone->add( days => $pickupdelay );
(-)a/tools/copy-holidays.pl (-38 lines)
Lines 1-38 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               = new CGI;
31
my $dbh                 = C4::Context->dbh();
32
33
my $branchcode          = $input->param('branchcode');
34
my $from_branchcode     = $input->param('from_branchcode');
35
36
C4::Calendar->new(branchcode => $from_branchcode)->copy_to_branch($branchcode) if $from_branchcode && $branchcode;
37
38
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=".($branchcode || $from_branchcode));
(-)a/tools/discrete_calendar.pl (+155 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 = new CGI;
30
31
# Get the template to use
32
my ($template, $loggedinuser, $cookie)
33
    = get_template_and_user({template_name => "tools/discrete_calendar.tt",
34
                             type => "intranet",
35
                             query => $input,
36
                             authnotrequired => 0,
37
                             flagsrequired => {tools => 'edit_calendar'},
38
                             debug => 1,
39
                           });
40
41
my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
42
my $calendar = Koha::DiscreteCalendar->new({branchcode => $branch});
43
#alert the user that they are using the default calendar because they do not have a library set
44
my $no_branch_selected = $calendar->{no_branch_selected};
45
46
my $weekday = $input->param('day_of_week');
47
48
my $holiday_type = $input->param('holidayType');
49
my $allbranches = $input->param('allBranches');
50
51
my $title = $input->param('Title');
52
53
my $action = $input->param('action') || '';
54
55
# calendardate - date passed in url for human readability (syspref)
56
# if the url has an invalid date default to 'now.'
57
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate')); } || dt_from_string;
58
my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
59
60
if($action eq 'copyBranch'){
61
    $calendar->copy_to_branch(scalar $input->param('newBranch'));
62
} elsif($action eq 'copyDates'){
63
    my $from_startDate = $input->param('from_copyFrom') ||'';
64
    my $from_endDate = $input->param('toDate') || '';
65
    my $to_startDate = $input->param('to_copyFrom') || '';
66
    my $to_endDate = $input->param('to_copyTo') || '';
67
    my $daysnumber= $input->param('daysnumber');
68
69
    $from_startDate = dt_from_string(scalar $from_startDate) if$from_startDate  ne '';
70
    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
71
    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
72
    $to_endDate = dt_from_string(scalar $to_endDate) if $to_endDate ne '';
73
74
    $calendar->copy_holiday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
75
} elsif($action eq 'edit'){
76
    my $openHour = $input->param('openHour');
77
    my $closeHour = $input->param('closeHour');
78
    my $endDate = $input->param('toDate');
79
    my $deleteType = $input->param('deleteType') || 0;
80
    #Get today from javascript for a precise local time
81
    my $local_today = dt_from_string( $input->param('local_today'), 'iso');
82
83
    my $startDate = dt_from_string(scalar $input->param('from_copyFrom'));
84
85
    if($endDate ne '' ) {
86
        $endDate = dt_from_string(scalar $endDate);
87
    } else{
88
        $endDate = $startDate->clone();
89
    }
90
91
    warn $startDate;
92
    warn $endDate;
93
    $calendar->edit_holiday( {
94
        title        => $title,
95
        weekday      => $weekday,
96
        holiday_type => $holiday_type,
97
        open_hour    => $openHour,
98
        close_hour   => $closeHour,
99
        start_date   => $startDate,
100
        end_date     => $endDate,
101
        delete_type  => $deleteType,
102
        today        => $local_today
103
    });
104
}
105
106
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
107
108
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
109
$keydate =~ s/-/\//g;
110
111
# Set all the branches.
112
if ( C4::Context->only_my_library ) {
113
    $branch = C4::Context->userenv->{'branch'};
114
}
115
116
# Get all the holidays
117
118
#discrete_calendar weekly holidays
119
my @week_days = $calendar->get_week_days_holidays();
120
121
#discrete_calendar repeatable holidays
122
my @repeatable_holidays = $calendar->get_repeatable_holidays();
123
124
#discrete_calendar unique holidays
125
my @unique_holidays =$calendar->get_unique_holidays();
126
#discrete_calendar floating holidays
127
my @float_holidays =$calendar->get_float_holidays();
128
#discrete_caledar need validation holidays
129
my @need_validation_holidays =$calendar->get_need_validation_holidays();
130
131
#Calendar maximum date
132
my $minDate = $calendar->get_min_date($branch);
133
134
#Calendar minimum date
135
my $maxDate = $calendar->get_max_date($branch);
136
137
my @datesInfos = $calendar->get_dates_info($branch);
138
139
$template->param(
140
    UNIQUE_HOLIDAYS          => \@unique_holidays,
141
    FLOAT_HOLIDAYS           => \@float_holidays,
142
    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
143
    REPEATABLE_HOLIDAYS      => \@repeatable_holidays,
144
    WEEKLY_HOLIDAYS          => \@week_days,
145
    calendardate             => $calendardate,
146
    keydate                  => $keydate,
147
    branch                   => $branch,
148
    minDate                  => $minDate,
149
    maxDate                  => $maxDate,
150
    datesInfos               => \@datesInfos,
151
    no_branch_selected       => $no_branch_selected,
152
);
153
154
# Shows the template with the real values replaced
155
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/exceptionHolidays.pl (-123 lines)
Lines 1-123 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 = new CGI;
15
my $dbh = C4::Context->dbh();
16
17
my $branchcode = $input->param('showBranchName');
18
my $weekday = $input->param('showWeekday');
19
my $day = $input->param('showDay');
20
my $month = $input->param('showMonth');
21
my $year = $input->param('showYear');
22
my $title = $input->param('showTitle');
23
my $description = $input->param('showDescription');
24
my $holidaytype = $input->param('showHolidayType');
25
my $datecancelrange_dt = eval { dt_from_string( scalar $input->param('datecancelrange') ) };
26
my $calendardate = sprintf("%04d-%02d-%02d", $year, $month, $day);
27
28
my $calendar = C4::Calendar->new(branchcode => $branchcode);
29
30
$title || ($title = '');
31
if ($description) {
32
    $description =~ s/\r/\\r/g;
33
    $description =~ s/\n/\\n/g;
34
} else {
35
    $description = '';
36
}   
37
38
# We make an array with holiday's days
39
my @holiday_list;
40
if ($datecancelrange_dt){
41
            my $first_dt = DateTime->new(year => $year, month  => $month,  day => $day);
42
43
            for (my $dt = $first_dt->clone();
44
                $dt <= $datecancelrange_dt;
45
                $dt->add(days => 1) )
46
                {
47
                push @holiday_list, $dt->clone();
48
                }
49
}
50
if ($input->param('showOperation') eq 'exception') {
51
	$calendar->insert_exception_holiday(day => $day,
52
										month => $month,
53
									    year => $year,
54
						                title => $title,
55
						                description => $description);
56
} elsif ($input->param('showOperation') eq 'exceptionrange' ) {
57
        if (@holiday_list){
58
            foreach my $date (@holiday_list){
59
                $calendar->insert_exception_holiday(
60
                    day         => $date->{local_c}->{day},
61
                    month       => $date->{local_c}->{month},
62
                    year       => $date->{local_c}->{year},
63
                    title       => $title,
64
                    description => $description
65
                    );
66
            }
67
        }
68
} elsif ($input->param('showOperation') eq 'edit') {
69
    if($holidaytype eq 'weekday') {
70
      $calendar->ModWeekdayholiday(weekday => $weekday,
71
                                   title => $title,
72
                                   description => $description);
73
    } elsif ($holidaytype eq 'daymonth') {
74
      $calendar->ModDaymonthholiday(day => $day,
75
                                    month => $month,
76
                                    title => $title,
77
                                    description => $description);
78
    } elsif ($holidaytype eq 'ymd') {
79
      $calendar->ModSingleholiday(day => $day,
80
                                  month => $month,
81
                                  year => $year,
82
                                  title => $title,
83
                                  description => $description);
84
    } elsif ($holidaytype eq 'exception') {
85
      $calendar->ModExceptionholiday(day => $day,
86
                                  month => $month,
87
                                  year => $year,
88
                                  title => $title,
89
                                  description => $description);
90
    }
91
} elsif ($input->param('showOperation') eq 'delete') {
92
	$calendar->delete_holiday(weekday => $weekday,
93
	                          day => $day,
94
  	                          month => $month,
95
				              year => $year);
96
}elsif ($input->param('showOperation') eq 'deleterange') {
97
    if (@holiday_list){
98
        foreach my $date (@holiday_list){
99
            $calendar->delete_holiday_range(weekday => $weekday,
100
                                            day => $date->{local_c}->{day},
101
                                            month => $date->{local_c}->{month},
102
                                            year => $date->{local_c}->{year});
103
            }
104
    }
105
}elsif ($input->param('showOperation') eq 'deleterangerepeat') {
106
    if (@holiday_list){
107
        foreach my $date (@holiday_list){
108
           $calendar->delete_holiday_range_repeatable(weekday => $weekday,
109
                                         day => $date->{local_c}->{day},
110
                                         month => $date->{local_c}->{month});
111
        }
112
    }
113
}elsif ($input->param('showOperation') eq 'deleterangerepeatexcept') {
114
    if (@holiday_list){
115
        foreach my $date (@holiday_list){
116
           $calendar->delete_exception_holiday_range(weekday => $weekday,
117
                                         day => $date->{local_c}->{day},
118
                                         month => $date->{local_c}->{month},
119
                                         year => $date->{local_c}->{year});
120
        }
121
    }
122
}
123
print $input->redirect("/cgi-bin/koha/tools/holidays.pl?branch=$branchcode&calendardate=$calendardate");
(-)a/tools/holidays.pl (-132 lines)
Lines 1-132 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 = new CGI;
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
                             authnotrequired => 0,
38
                             flagsrequired => {tools => 'edit_calendar'},
39
                             debug => 1,
40
                           });
41
42
# calendardate - date passed in url for human readability (syspref)
43
# if the url has an invalid date default to 'now.'
44
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate') ); } || dt_from_string;
45
my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
46
47
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
48
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
49
$keydate =~ s/-/\//g;
50
51
my $branch= $input->param('branch') || C4::Context->userenv->{'branch'};
52
53
# Get all the holidays
54
55
my $calendar = C4::Calendar->new(branchcode => $branch);
56
my $week_days_holidays = $calendar->get_week_days_holidays();
57
my @week_days;
58
foreach my $weekday (keys %$week_days_holidays) {
59
# warn "WEEK DAY : $weekday";
60
    my %week_day;
61
    %week_day = (KEY => $weekday,
62
                 TITLE => $week_days_holidays->{$weekday}{title},
63
                 DESCRIPTION => $week_days_holidays->{$weekday}{description});
64
    push @week_days, \%week_day;
65
}
66
67
my $day_month_holidays = $calendar->get_day_month_holidays();
68
my @day_month_holidays;
69
foreach my $monthDay (keys %$day_month_holidays) {
70
    # Determine date format on month and day.
71
    my $day_monthdate;
72
    my $day_monthdate_sort;
73
    if (C4::Context->preference("dateformat") eq "metric") {
74
      $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
75
      $day_monthdate = "$day_month_holidays->{$monthDay}{day}/$day_month_holidays->{$monthDay}{month}";
76
    } elsif (C4::Context->preference("dateformat") eq "dmydot") {
77
      $day_monthdate_sort = "$day_month_holidays->{$monthDay}{month}.$day_month_holidays->{$monthDay}{day}";
78
      $day_monthdate = "$day_month_holidays->{$monthDay}{day}.$day_month_holidays->{$monthDay}{month}";
79
    }elsif (C4::Context->preference("dateformat") eq "us") {
80
      $day_monthdate = "$day_month_holidays->{$monthDay}{month}/$day_month_holidays->{$monthDay}{day}";
81
      $day_monthdate_sort = $day_monthdate;
82
    } else {
83
      $day_monthdate = "$day_month_holidays->{$monthDay}{month}-$day_month_holidays->{$monthDay}{day}";
84
      $day_monthdate_sort = $day_monthdate;
85
    }
86
    my %day_month;
87
    %day_month = (KEY => $monthDay,
88
                  DATE_SORT => $day_monthdate_sort,
89
                  DATE => $day_monthdate,
90
                  TITLE => $day_month_holidays->{$monthDay}{title},
91
                  DESCRIPTION => $day_month_holidays->{$monthDay}{description});
92
    push @day_month_holidays, \%day_month;
93
}
94
95
my $exception_holidays = $calendar->get_exception_holidays();
96
my @exception_holidays;
97
foreach my $yearMonthDay (keys %$exception_holidays) {
98
    my $exceptiondate = eval { dt_from_string( $exception_holidays->{$yearMonthDay}{date} ) };
99
    my %exception_holiday;
100
    %exception_holiday = (KEY => $yearMonthDay,
101
                          DATE_SORT => $exception_holidays->{$yearMonthDay}{date},
102
                          DATE => output_pref( { dt => $exceptiondate, dateonly => 1 } ),
103
                          TITLE => $exception_holidays->{$yearMonthDay}{title},
104
                          DESCRIPTION => $exception_holidays->{$yearMonthDay}{description});
105
    push @exception_holidays, \%exception_holiday;
106
}
107
108
my $single_holidays = $calendar->get_single_holidays();
109
my @holidays;
110
foreach my $yearMonthDay (keys %$single_holidays) {
111
    my $holidaydate_dt = eval { dt_from_string( $single_holidays->{$yearMonthDay}{date} ) };
112
    my %holiday;
113
    %holiday = (KEY => $yearMonthDay,
114
                DATE_SORT => $single_holidays->{$yearMonthDay}{date},
115
                DATE => output_pref( { dt => $holidaydate_dt, dateonly => 1 } ),
116
                TITLE => $single_holidays->{$yearMonthDay}{title},
117
                DESCRIPTION => $single_holidays->{$yearMonthDay}{description});
118
    push @holidays, \%holiday;
119
}
120
121
$template->param(
122
    WEEK_DAYS_LOOP           => \@week_days,
123
    HOLIDAYS_LOOP            => \@holidays,
124
    EXCEPTION_HOLIDAYS_LOOP  => \@exception_holidays,
125
    DAY_MONTH_HOLIDAYS_LOOP  => \@day_month_holidays,
126
    calendardate             => $calendardate,
127
    keydate                  => $keydate,
128
    branch                   => $branch,
129
);
130
131
# Shows the template with the real values replaced
132
output_html_with_http_headers $input, $cookie, $template->output;
(-)a/tools/newHolidays.pl (-148 lines)
Lines 1-147 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 Koha::Caches;
28
29
use C4::Calendar;
30
use DateTime;
31
use Koha::DateUtils;
32
33
my $input               = new CGI;
34
my $dbh                 = C4::Context->dbh();
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
    # we updated the single_holidays table, so wipe its cache
145
    my $cache = Koha::Caches->get_instance();
146
    $cache->clear_from_cache( 'single_holidays') ;
147
}
148
- 

Return to bug 17015