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