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

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

Return to bug 17015