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

(-)a/Koha/DiscreteCalendar.pm (+1016 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 strict;
20
use warnings;
21
22
use CGI qw ( -utf8 );
23
use Carp;
24
use DateTime;
25
use DateTime::Format::Strptime;
26
27
use C4::Context;
28
use C4::Output;
29
use Koha::Database;
30
use Koha::DateUtils;
31
32
sub new {
33
    my ( $classname, %options ) = @_;
34
    my $self = {};
35
    bless $self, $classname;
36
    for my $o_name ( keys %options ) {
37
        my $o = lc $o_name;
38
        $self->{$o} = $options{$o_name};
39
    }
40
    if ( !defined $self->{branchcode} ) {
41
        croak 'No branchcode argument passed to Koha::DiscreteCalendar->new';
42
    }
43
    $self->_init();
44
45
    return $self;
46
}
47
48
sub _init {
49
    my $self = shift;
50
    $self->{days_mode} = C4::Context->preference('useDaysMode');
51
    #If the branchcode doesn't exist we use the default calendar.
52
    my $schema = Koha::Database->new->schema;
53
    my $branchcode = $self->{branchcode};
54
    my $dtf = $schema->storage->datetime_parser;
55
    my $today = $dtf->format_datetime(DateTime->today);
56
    my $rs = $schema->resultset('DiscreteCalendar')->single(
57
        {
58
            branchcode  => $branchcode,
59
            date        => $today
60
        }
61
    );
62
    #use default if no calendar is found
63
    if (!$rs){
64
        $self->{branchcode} = 'DFLT';
65
        $self->{no_branch_selected} = 1;
66
    }
67
68
}
69
70
# Returns an array of all the date objects of current branch.
71
sub getDatesInfo {
72
    my $self = shift;
73
    my $branchcode = $self->{branchcode};
74
    my @datesInfos =();
75
    my $schema = Koha::Database->new->schema;
76
77
    my $rs = $schema->resultset('DiscreteCalendar')->search(
78
        {
79
            branchcode  => $branchcode
80
        },
81
        {
82
            select  => [ 'date', { DATE => 'date' } ],
83
            as      => [qw/ date date /],
84
            columns =>[ qw/ holidaytype openhour closehour note/]
85
        },
86
    );
87
88
    while (my $date = $rs->next()){
89
        my $outputdate = dt_from_string( $date->date(), 'iso');
90
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
91
        push @datesInfos, {
92
            date        => $date->date(),
93
            outputdate  => $outputdate,
94
            holidaytype => $date->holidaytype() ,
95
            openhour    => $date->openhour(),
96
            closehour   => $date->closehour(),
97
            note        => $date->note()
98
        };
99
    }
100
101
    return @datesInfos;
102
}
103
104
# This methode will copy everything from a given branch to a new branch
105
sub add_new_branch {
106
    my ($self, $copyBranch, $newBranch) = @_;
107
    $copyBranch = 'DFLT' unless $copyBranch;
108
    my $schema = Koha::Database->new->schema;
109
110
    my $branch_rs = $schema->resultset('DiscreteCalendar')->search({
111
            branchcode => $copyBranch
112
    });
113
114
    while(my $row = $branch_rs->next()){
115
        $schema->resultset('DiscreteCalendar')->create({
116
            date        => $row->date(),
117
            branchcode  => $newBranch,
118
            isopened    => $row->isopened(),
119
            holidaytype => $row->holidaytype(),
120
            openhour    => $row->openhour(),
121
            closehour   => $row->closehour(),
122
        });
123
    }
124
125
}
126
127
# DiscreteCalendar data transfer object (DTO)
128
sub get_date_info {
129
    my ($self, $date) = @_;
130
    my $branchcode = $self->{branchcode};
131
    my $schema = Koha::Database->new->schema;
132
    my $dtf = $schema->storage->datetime_parser;
133
    #String dates for Database usage
134
    my $date_string = $dtf->format_datetime($date);
135
136
    my $rs = $schema->resultset('DiscreteCalendar')->search(
137
        {
138
            branchcode  => $branchcode,
139
        },
140
        {
141
            select  => [ 'date', { DATE => 'date' } ],
142
            as      => [qw/ date date /],
143
            where   => \['DATE(?) = date', $date_string ],
144
            columns =>[ qw/ branchcode holidaytype openhour closehour note/]
145
        },
146
    );
147
    my $dateDTO;
148
    while (my $date = $rs->next()){
149
        $dateDTO = {
150
            date        => $date->date(),
151
            branchcode  => $date->branchcode(),
152
            holidaytype => $date->holidaytype() ,
153
            openhour    => $date->openhour(),
154
            closehour   => $date->closehour(),
155
            note        => $date->note()
156
        };
157
    }
158
159
    return $dateDTO;
160
}
161
162
# Returns the furthest date available in the databse of current branch.
163
sub getMaxDate {
164
    my $self       = shift;
165
    my $branchcode     = $self->{branchcode};
166
    my $schema = Koha::Database->new->schema;
167
168
    my $rs = $schema->resultset('DiscreteCalendar')->search(
169
        {
170
            branchcode  => $branchcode
171
        },
172
        {
173
            select => [{ MAX => 'date' } ],
174
            as     => [qw/ max /],
175
        }
176
    );
177
178
    return $rs->next()->get_column('max');
179
}
180
181
# Returns the onldest date available in the databse of current branch.
182
sub getMinDate {
183
    my $self       = shift;
184
    my $branchcode     = $self->{branchcode};
185
    my $schema = Koha::Database->new->schema;
186
187
    my $rs = $schema->resultset('DiscreteCalendar')->search(
188
        {
189
            branchcode  => $branchcode
190
        },
191
        {
192
            select => [{ MIN => 'date' } ],
193
            as     => [qw/ min /],
194
        }
195
    );
196
197
    return $rs->next()->get_column('min');
198
}
199
200
# Returns an array of all the date objects that are unique holidays.
201
sub get_unique_holidays {
202
    my $self = shift;
203
    my $branchcode = $self->{branchcode};
204
    my @unique_holidays;
205
    my $schema = Koha::Database->new->schema;
206
207
    my $rs = $schema->resultset('DiscreteCalendar')->search(
208
        {
209
            branchcode  => $branchcode,
210
            holidaytype => 'E'
211
        },
212
        {
213
            select => [{ DATE => 'date' }, 'note' ],
214
            as     => [qw/ date note/],
215
        }
216
    );
217
    while (my $date = $rs->next()){
218
        my $outputdate = dt_from_string($date->date(), 'iso');
219
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
220
        push @unique_holidays, {
221
            date =>  $date->date(),
222
            outputdate => $outputdate,
223
            note => $date->note()
224
        }
225
    }
226
227
    return @unique_holidays;
228
}
229
230
# Returns an array of all the date objects  that are float holidays.
231
sub get_float_holidays {
232
    my $self = shift;
233
    my $branchcode = $self->{branchcode};
234
    my @float_holidays;
235
    my $schema = Koha::Database->new->schema;
236
237
    my $rs = $schema->resultset('DiscreteCalendar')->search(
238
        {
239
            branchcode  => $branchcode,
240
            holidaytype => 'F'
241
        },
242
        {
243
            select => [{ DATE => 'date' }, 'note' ],
244
            as     => [qw/ date note/],
245
        }
246
    );
247
    while (my $date = $rs->next()){
248
        my $outputdate = dt_from_string($date->date(), 'iso');
249
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
250
        push @float_holidays, {
251
            date        =>  $date->date(),
252
            outputdate  => $outputdate,
253
            note        => $date->note()
254
        }
255
    }
256
257
    return @float_holidays;
258
}
259
260
# Returns an array of all the date objects  that are need validation holidays.
261
sub get_need_valdation_holidays {
262
    my $self = shift;
263
    my $branchcode = $self->{branchcode};
264
    my @need_validation_holidays;
265
    my $schema = Koha::Database->new->schema;
266
267
    my $rs = $schema->resultset('DiscreteCalendar')->search(
268
        {
269
            branchcode  => $branchcode,
270
            holidaytype => 'N'
271
        },
272
        {
273
            select => [{ DATE => 'date' }, 'note' ],
274
            as     => [qw/ date note/],
275
        }
276
    );
277
    while (my $date = $rs->next()){
278
        my $outputdate = dt_from_string($date->date(), 'iso');
279
        $outputdate = output_pref( { dt => $outputdate, dateonly => 1 } );
280
        push @need_validation_holidays, {
281
            date        =>  $date->date(),
282
            outputdate  => $outputdate,
283
            note        => $date->note()
284
        }
285
    }
286
287
    return @need_validation_holidays;
288
}
289
290
# Returns an array of all the date objects  that are repeatable holidays.
291
sub get_repeatable_holidays {
292
    my $self = shift;
293
    my $branchcode = $self->{branchcode};
294
    my @repeatable_holidays;
295
    my $schema = Koha::Database->new->schema;
296
297
    my $rs = $schema->resultset('DiscreteCalendar')->search(
298
        {
299
            branchcode  => $branchcode,
300
            holidaytype => 'R',
301
302
        },
303
        {
304
            select  => \[ 'distinct DAY(date), MONTH(date), note'],
305
            as      => [qw/ day month note/],
306
        }
307
    );
308
309
    while (my $date = $rs->next()){
310
        push @repeatable_holidays, {
311
            day=> $date->get_column('day'),
312
            month => $date->get_column('month'),
313
            note => $date->note()
314
        };
315
    }
316
317
    return @repeatable_holidays;
318
}
319
320
# Returns an array of all the date objects  that are weekly holidays.
321
sub get_week_days_holidays {
322
    my $self = shift;
323
    my $branchcode = $self->{branchcode};
324
    my @week_days;
325
    my $schema = Koha::Database->new->schema;
326
327
    my $rs = $schema->resultset('DiscreteCalendar')->search(
328
        {
329
            holidaytype => 'W',
330
            branchcode  => $branchcode,
331
        },
332
        {
333
            select      => [{ DAYOFWEEK => 'date'}, 'note'],
334
            as          => [qw/ weekday note /],
335
            distinct    => 1,
336
        }
337
    );
338
339
    while (my $date = $rs->next()){
340
        push @week_days, {
341
            weekday => ($date->get_column('weekday') -1),
342
            note    => $date->note()
343
        };
344
    }
345
346
    return @week_days;
347
}
348
349
=head1 edit_holiday
350
351
Modifies a date or a range of dates
352
353
C<$title> Is the title to be modified for the holiday formed by $year/$month/$day.
354
355
C<$weekday> Is the day of week for the holiday or the value everyday when it's for the whole range.
356
357
C<$holidaytype> Is the type of the holiday :
358
    E : Exception holiday, single day.
359
    F : Floating holiday, different day each year.
360
    N : Needs validation, copied float holiday from the past
361
    R : Repeatable holiday, repeated on same date.
362
    W : Weekly holiday, same day of the week.
363
364
C<$openHour> Is the opening hour.
365
C<$closeHour> Is the closing hour.
366
C<$startDate> Is the start of the range of dates.
367
C<$endDate> Is the end of the range of dates.
368
C<$deleteType> Delete all
369
C<$today> Today based on the local date, using JavaScript.
370
371
=cut
372
373
sub edit_holiday {
374
    my ($self, $title, $weekday, $holidaytype, $openHour, $closeHour, $startDate, $endDate, $deleteType, $today) = @_;
375
    my $branchcode = $self->{branchcode};
376
    my $schema = Koha::Database->new->schema;
377
    $schema->{AutoCommit} = 0;
378
    $schema->storage->txn_begin;
379
    my $dtf = $schema->storage->datetime_parser;
380
    #String dates for Database usage
381
    my $startDate_String = $dtf->format_datetime($startDate);
382
    my $endDate_String = $dtf->format_datetime($endDate);
383
    $today = DateTime->today unless $today;
384
    $today = $dtf->format_datetime($today);
385
386
    my %updateValues = (
387
        isopened    => 0,
388
        note        => $title,
389
        holidaytype => $holidaytype,
390
    );
391
    $updateValues{openhour}  = $openHour if $openHour ne '';
392
    $updateValues{closehour}  = $closeHour if $closeHour ne '';
393
394
    if($holidaytype eq 'W') {
395
        #Update weekly holidays
396
        if($startDate_String eq $endDate_String ){
397
            $endDate_String = $self->getMaxDate();
398
        }
399
        my $rs = $schema->resultset('DiscreteCalendar')->search(
400
            {
401
                branchcode  => $branchcode,
402
            },
403
            {
404
                where => \[ 'DAYOFWEEK(date) = ? AND date >= ? AND date <= DATE(?)', $weekday, $today, $endDate_String],
405
            }
406
        );
407
408
        while (my $date = $rs->next()){
409
            $date->update(\%updateValues);
410
        }
411
    }elsif ($holidaytype eq 'E' || $holidaytype eq 'F' || $holidaytype eq 'N') {
412
        #Update Exception Float and Needs Validation holidays
413
        my $where = \['date BETWEEN DATE(?) AND DATE(?) AND date >= ?',$startDate_String, $endDate_String, $today];
414
        if($startDate_String ne $endDate_String && $weekday && $weekday ne 'everyday'){
415
            $where = \['date BETWEEN DATE(?) AND DATE(?) AND date >= ? AND DAYOFWEEK(date) = ?',$startDate_String, $endDate_String, $today, $weekday];
416
        }
417
        my $rs = $schema->resultset('DiscreteCalendar')->search(
418
            {
419
                branchcode  => $branchcode,
420
            },
421
            {
422
                where =>  $where,
423
            }
424
        );
425
        while (my $date = $rs->next()){
426
            $date->update(\%updateValues);
427
        }
428
429
    }elsif ($holidaytype eq 'R') {
430
        #Update repeatable holidays
431
        my $parser = DateTime::Format::Strptime->new(
432
           pattern  => '%m-%d',
433
           on_error => 'croak',
434
        );
435
        #Format the dates to have only month-day ex: 01-04 for January 4th
436
        $startDate = $parser->format_datetime($startDate);
437
        $endDate = $parser->format_datetime($endDate);
438
        my $rs = $schema->resultset('DiscreteCalendar')->search(
439
            {
440
                branchcode  => $branchcode,
441
            },
442
            {
443
                where =>  \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ? ) AND date >= ?", $startDate, $endDate, $today],
444
            }
445
        );
446
        while (my $date = $rs->next()){
447
            $date->update(\%updateValues);
448
        }
449
450
    }else {
451
        #Update date(s)/Remove holidays
452
        my $where = \['date BETWEEN DATE(?) AND DATE(?) AND date >= ?',$startDate_String, $endDate_String, $today];
453
        if($startDate_String ne $endDate_String && $weekday && $weekday ne 'everyday'){
454
            $where = \['date BETWEEN DATE(?) AND DATE(?) AND date >= ? AND DAYOFWEEK(date) = ?',$startDate_String, $endDate_String, $today, $weekday];
455
        }
456
        my $rs = $schema->resultset('DiscreteCalendar')->search(
457
            {
458
                branchcode  => $branchcode,
459
            },
460
            {
461
                where =>  $where,
462
            }
463
        );
464
        #If none, the date(s) will be normal days, else,
465
        if($holidaytype eq 'none'){
466
            $updateValues{holidaytype}  ='';
467
            $updateValues{isopened}  =1;
468
        }else{
469
            delete $updateValues{holidaytype};
470
        }
471
472
        while (my $date = $rs->next()){
473
            if($deleteType){
474
                if($date->holidaytype() eq 'W'){
475
                    $self->remove_weekly_holidays($weekday, \%updateValues, $today);
476
                }elsif($date->holidaytype() eq 'R'){
477
                    $self->remove_repeatable_holidays($startDate, $endDate, \%updateValues, $today);
478
                }
479
            }else{
480
                $date->update(\%updateValues);
481
            }
482
        }
483
    }
484
    $schema->storage->txn_commit;
485
}
486
487
sub remove_weekly_holidays {
488
    my ($self, $weekday, $updateValues, $today) = @_;
489
    my $branchcode = $self->{branchcode};
490
    my $schema = Koha::Database->new->schema;
491
492
    my $rs = $schema->resultset('DiscreteCalendar')->search(
493
        {
494
            branchcode  => $branchcode,
495
            isopened    => 0,
496
            holidaytype => 'W'
497
        },
498
        {
499
            where => \["DAYOFWEEK(date) = ? AND date >= ?", $weekday,$today],
500
        }
501
    );
502
503
    while (my $date = $rs->next()){
504
        $date->update($updateValues);
505
    }
506
}
507
508
sub remove_repeatable_holidays {
509
    my ($self, $startDate, $endDate, $updateValues, $today) = @_;
510
    my $branchcode = $self->{branchcode};
511
    my $schema = Koha::Database->new->schema;
512
    my $parser = DateTime::Format::Strptime->new(
513
        pattern   => '%m-%d',
514
        on_error  => 'croak',
515
    );
516
    #Format the dates to have only month-day ex: 01-04 for January 4th
517
    $startDate = $parser->format_datetime($startDate);
518
    $endDate = $parser->format_datetime($endDate);
519
520
    my $rs = $schema->resultset('DiscreteCalendar')->search(
521
        {
522
            branchcode  => $branchcode,
523
            isopened    => 0,
524
            holidaytype => 'R',
525
        },
526
        {
527
            where =>  \["(DATE_FORMAT(date,'\%m-\%d') BETWEEN ? AND ? ) AND date >= ?", $startDate, $endDate, $today],
528
        }
529
    );
530
531
    while (my $date = $rs->next()){
532
        $date->update($updateValues);
533
    }
534
}
535
536
sub copyToBranch {
537
    my ($self,$newBranch) =@_;
538
    my $branchcode = $self->{branchcode};
539
    my $schema = Koha::Database->new->schema;
540
541
    my $copyFrom = $schema->resultset('DiscreteCalendar')->search(
542
        {
543
            branchcode  => $branchcode
544
        },
545
        {
546
            columns     => [ qw/ date isopened note holidaytype openhour closehour /]
547
        }
548
    );
549
    while (my $copyDate = $copyFrom->next()){
550
        my $copyTo = $schema->resultset('DiscreteCalendar')->search(
551
            {
552
                branchcode  => $newBranch,
553
                date        => $copyDate->date(),
554
            },
555
            {
556
                columns => [ qw/ date branchcode isopened note holidaytype openhour closehour /]
557
            }
558
        );
559
        #if the date does not exist in the copyTO branch, than skip it.
560
        if($copyTo->count ==0){
561
            next;
562
        }
563
        $copyTo->next()->update({
564
            isopened    => $copyDate->isopened(),
565
            holidaytype => $copyDate->holidaytype(),
566
            note        => $copyDate->note(),
567
            openhour    => $copyDate->openhour(),
568
            closehour   => $copyDate->closehour()
569
        });
570
    }
571
}
572
573
sub isOpened {
574
    my($self, $date) = @_;
575
    my $branchcode = $self->{branchcode};
576
    my $schema = Koha::Database->new->schema;
577
    my $dtf = $schema->storage->datetime_parser;
578
    $date= $dtf->format_datetime($date);
579
    #if the date is not found
580
    my $isOpened = -1;
581
    my $rs = $schema->resultset('DiscreteCalendar')->search(
582
        {
583
            branchcode => $branchcode,
584
        },
585
        {
586
            where   => \['date = DATE(?)', $date]
587
        }
588
    );
589
    $isOpened = $rs->next()->isopened() if $rs->count() != 0;
590
591
    return $isOpened;
592
}
593
594
sub is_holiday {
595
    my($self, $date) = @_;
596
    my $branchcode = $self->{branchcode};
597
    my $schema = Koha::Database->new->schema;
598
    my $dtf = $schema->storage->datetime_parser;
599
    $date= $dtf->format_datetime($date);
600
    #if the date is not found
601
    my $isHoliday = -1;
602
    my $rs = $schema->resultset('DiscreteCalendar')->search(
603
        {
604
            branchcode => $branchcode,
605
        },
606
        {
607
            where   => \['date = DATE(?)', $date]
608
        }
609
    );
610
611
    if($rs->count() != 0){
612
        $isHoliday = 0 if $rs->first()->isopened();
613
        $isHoliday = 1 if !$rs->first()->isopened();
614
    }
615
616
    return $isHoliday;
617
}
618
619
sub copyHoliday {
620
    my ($self, $from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber) = @_;
621
    my $branchcode = $self->{branchcode};
622
    my $copyFromType =  $from_startDate && $from_endDate eq '' ? 'oneDay': 'range';
623
    my $schema = Koha::Database->new->schema;
624
    my $dtf = $schema->storage->datetime_parser;
625
626
    if ($copyFromType eq 'oneDay'){
627
        my $where;
628
        $to_startDate = $dtf->format_datetime($to_startDate);
629
        if ($to_startDate && $to_endDate) {
630
            $to_endDate = $dtf->format_datetime($to_endDate);
631
            $where = \["date BETWEEN ? AND ?", $to_startDate, $to_endDate];
632
        } else {
633
            $where = \['date = ?', $to_startDate];
634
        }
635
636
        $from_startDate = $dtf->format_datetime($from_startDate);
637
        my $fromDate = $schema->resultset('DiscreteCalendar')->search(
638
            {
639
                branchcode  => $branchcode,
640
                date        => $from_startDate
641
            }
642
        );
643
        my $toDates = $schema->resultset('DiscreteCalendar')->search(
644
            {
645
                branchcode  => $branchcode,
646
            },
647
            {
648
                where       => $where
649
            }
650
        );
651
652
        my $copyDate = $fromDate->next();
653
        while (my $date = $toDates->next()){
654
            $date->update({
655
                isopened    => $copyDate->isopened(),
656
                holidaytype => $copyDate->holidaytype(),
657
                note        => $copyDate->note(),
658
                openhour    => $copyDate->openhour(),
659
                closehour   => $copyDate->closehour()
660
            })
661
        }
662
663
    }else{
664
        my $endDate = dt_from_string($from_endDate);
665
        $to_startDate = $dtf->format_datetime($to_startDate);
666
        $to_endDate = $dtf->format_datetime($to_endDate);
667
        if($daysnumber ==7){
668
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
669
                my $formatedDate = $dtf->format_datetime($tempDate);
670
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
671
                    {
672
                        branchcode  => $branchcode,
673
                        date        => $formatedDate,
674
                    },
675
                    {
676
                        select  => [{ DAYOFWEEK => 'date' }],
677
                        as      => [qw/ weekday /],
678
                        columns =>[ qw/ holidaytype note openhour closehour note/]
679
                    }
680
                );
681
                my $copyDate = $fromDate->next();
682
                my $weekday = $copyDate->get_column('weekday');
683
684
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
685
                    {
686
                        branchcode  => $branchcode,
687
688
                    },
689
                    {
690
                        where       => \['date BETWEEN ? AND ? AND DAYOFWEEK(date) = ?',$to_startDate, $to_endDate, $weekday]
691
                    }
692
                );
693
                my $copyToDate = $toDate->next();
694
                $copyToDate->update({
695
                    isopened    => $copyDate->isopened(),
696
                    holidaytype => $copyDate->holidaytype(),
697
                    note        => $copyDate->note(),
698
                    openhour    => $copyDate->openhour(),
699
                    closehour   => $copyDate->closehour()
700
                });
701
702
            }
703
        }else{
704
            my $to_startDate = dt_from_string($to_startDate);
705
            my $to_endDate = dt_from_string($to_endDate);
706
            for (my $tempDate = $from_startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
707
                my $from_formatedDate = $dtf->format_datetime($tempDate);
708
                my $fromDate = $schema->resultset('DiscreteCalendar')->search(
709
                    {
710
                        branchcode  => $branchcode,
711
                        date        => $from_formatedDate,
712
                    },
713
                    {
714
                        order_by    => { -asc => 'date' }
715
                    }
716
                );
717
                my $to_formatedDate = $dtf->format_datetime($to_startDate);
718
                my $toDate = $schema->resultset('DiscreteCalendar')->search(
719
                    {
720
                        branchcode  => $branchcode,
721
                        date        => $to_formatedDate
722
                    },
723
                    {
724
                        order_by    => { -asc => 'date' }
725
                    }
726
                );
727
                my $copyDate = $fromDate->next();
728
                $toDate->next()->update({
729
                    isopened    => $copyDate->isopened(),
730
                    holidaytype => $copyDate->holidaytype(),
731
                    note        => $copyDate->note(),
732
                    openhour    => $copyDate->openhour(),
733
                    closehour   => $copyDate->closehour()
734
                });
735
                $to_startDate->add(days =>1);
736
            }
737
        }
738
739
740
    }
741
}
742
743
sub days_between {
744
    my ($self, $start_date, $end_date, ) = @_;
745
    my $branchcode = $self->{branchcode};
746
747
    if ( $start_date->compare($end_date) > 0 ) {
748
        # swap dates
749
        my $int_dt = $end_date;
750
        $end_date = $start_date;
751
        $start_date = $int_dt;
752
    }
753
754
    my $schema = Koha::Database->new->schema;
755
    my $dtf = $schema->storage->datetime_parser;
756
    $start_date = $dtf->format_datetime($start_date);
757
    $end_date = $dtf->format_datetime($end_date);
758
759
    my $days_between = $schema->resultset('DiscreteCalendar')->search(
760
        {
761
            branchcode  => $branchcode,
762
            isopened    => 1,
763
        },
764
        {
765
            where       => \['date >= date(?) AND date < date(?)',$start_date, $end_date]
766
        }
767
    );
768
769
    return DateTime::Duration->new( days => $days_between->count());
770
}
771
772
sub next_open_day {
773
    my ( $self, $date ) = @_;
774
    my $branchcode = $self->{branchcode};
775
    my $schema = Koha::Database->new->schema;
776
    my $dtf = $schema->storage->datetime_parser;
777
    $date = $dtf->format_datetime($date);
778
779
    my $rs = $schema->resultset('DiscreteCalendar')->search(
780
        {
781
            branchcode  => $branchcode,
782
            isopened    => 1,
783
        },
784
        {
785
            where       => \['date > date(?)', $date],
786
            order_by    => { -asc => 'date' },
787
            rows        => 1
788
        }
789
    );
790
    return dt_from_string( $rs->next()->date(), 'iso');
791
}
792
793
sub prev_open_day {
794
    my ( $self, $date ) = @_;
795
    my $branchcode = $self->{branchcode};
796
    my $schema = Koha::Database->new->schema;
797
    my $dtf = $schema->storage->datetime_parser;
798
    $date = $dtf->format_datetime($date);
799
800
    my $rs = $schema->resultset('DiscreteCalendar')->search(
801
        {
802
            branchcode  => $branchcode,
803
            isopened    => 1,
804
        },
805
        {
806
            where       => \['date < date(?)', $date],
807
            order_by    => { -desc => 'date' },
808
            rows        => 1
809
        }
810
    );
811
    return dt_from_string( $rs->next()->date(), 'iso');
812
}
813
814
sub days_forward {
815
    my $self     = shift;
816
    my $start_dt = shift;
817
    my $num_days = shift;
818
819
    return $start_dt unless $num_days > 0;
820
821
    my $base_dt = $start_dt->clone();
822
823
    while ($num_days--) {
824
        $base_dt = $self->next_open_day($base_dt);
825
    }
826
827
    return $base_dt;
828
}
829
830
sub hours_between {
831
    my ($self, $start_dt, $end_dt) = @_;
832
    my $branchcode = $self->{branchcode};
833
    my $schema = Koha::Database->new->schema;
834
    my $dtf = $schema->storage->datetime_parser;
835
    my $start_date = $start_dt->clone();
836
    my $end_date = $end_dt->clone();
837
    my $duration = $end_date->delta_ms($start_date);
838
    $start_date->truncate( to => 'day' );
839
    $end_date->truncate( to => 'day' );
840
841
    # NB this is a kludge in that it assumes all days are 24 hours
842
    # However for hourly loans the logic should be expanded to
843
    # take into account open/close times then it would be a duration
844
    # of library open hours
845
    my $skipped_days = 0;
846
    $start_date = $dtf->format_datetime($start_date);
847
    $end_date = $dtf->format_datetime($end_date);
848
    my $hours_between = $schema->resultset('DiscreteCalendar')->search(
849
        {
850
            branchcode  =>  $branchcode,
851
            isopened    => 0
852
        },
853
        {
854
            where  => \[ 'date BETWEEN ? AND ?', $start_date, $end_date],
855
        }
856
    );
857
858
    if ($skipped_days = $hours_between->count()) {
859
        $duration->subtract_duration(DateTime::Duration->new( hours => 24 * $skipped_days));
860
    }
861
862
    return $duration;
863
}
864
865
sub open_hours_between {
866
    my ($self, $start_date, $end_date) = @_;
867
    my $branchcode = $self->{branchcode};
868
    my $schema = Koha::Database->new->schema;
869
    my $dtf = $schema->storage->datetime_parser;
870
    $start_date = $dtf->format_datetime($start_date);
871
    $end_date = $dtf->format_datetime($end_date);
872
873
    my $working_hours_between = $schema->resultset('DiscreteCalendar')->search(
874
        {
875
            branchcode  => $branchcode,
876
            isopened    => 1,
877
        },
878
        {
879
            select  => \['sum(time_to_sec(timediff(closehour, openhour)) / 3600)'],
880
            as      => [qw /hours_between/],
881
            where   => \['date BETWEEN DATE(?) AND DATE(?)', $start_date, $end_date]
882
        }
883
    );
884
885
    my $loan_day = $schema->resultset('DiscreteCalendar')->search(
886
        {
887
            branchcode  => $branchcode,
888
        },
889
        {
890
            where       => \['date = DATE(?)', $start_date],
891
        }
892
    );
893
894
    my $return_day = $schema->resultset('DiscreteCalendar')->search(
895
        {
896
            branchcode  => $branchcode,
897
        },
898
        {
899
            where       => \['date = DATE(?)', $end_date],
900
        }
901
    );
902
903
    #Capture the time portion of the date
904
    $start_date =~ /\s(.*)/;
905
    my $loan_date_time = $1;
906
    $end_date =~ /\s(.*)/;
907
    my $return_date_time = $1;
908
909
    my $not_used_hours = $schema->resultset('DiscreteCalendar')->search(
910
        {
911
            branchcode  => $branchcode,
912
            isopened    => 1,
913
        },
914
        {
915
            select  => \[ '(time_to_sec(timediff(?, ?)) + time_to_sec(timediff(?, ?)) ) / 3600', $return_day->next()->closehour(), $return_date_time, $loan_date_time, $loan_day->next()->openhour()],
916
            as      => [qw /not_used_hours/],
917
        }
918
    );
919
920
    return ($working_hours_between->next()->get_column('hours_between') - $not_used_hours->next()->get_column('not_used_hours'));
921
}
922
sub addDate {
923
    my ( $self, $startdate, $add_duration, $unit ) = @_;
924
925
    # Default to days duration (legacy support I guess)
926
    if ( ref $add_duration ne 'DateTime::Duration' ) {
927
        $add_duration = DateTime::Duration->new( days => $add_duration );
928
    }
929
930
    $unit ||= 'days'; # default days ?
931
    my $dt;
932
933
    if ( $unit eq 'hours' ) {
934
        # Fixed for legacy support. Should be set as a branch parameter
935
        my $return_by_hour = 10;
936
937
        $dt = $self->addHours($startdate, $add_duration, $return_by_hour);
938
    } else {
939
        # days
940
        $dt = $self->addDays($startdate, $add_duration);
941
    }
942
943
    return $dt;
944
}
945
946
sub addHours {
947
    my ( $self, $startdate, $hours_duration, $return_by_hour ) = @_;
948
    my $base_date = $startdate->clone();
949
950
    $base_date->add_duration($hours_duration);
951
952
    # If we are using the calendar behave for now as if Datedue
953
    # was the chosen option (current intended behaviour)
954
955
    if ( $self->{days_mode} ne 'Days' &&
956
    $self->is_holiday($base_date) ) {
957
958
        if ( $hours_duration->is_negative() ) {
959
            $base_date = $self->prev_open_day($base_date);
960
        } else {
961
            $base_date = $self->next_open_day($base_date);
962
        }
963
964
        $base_date->set_hour($return_by_hour);
965
966
    }
967
968
    return $base_date;
969
}
970
971
sub addDays {
972
    my ( $self, $startdate, $days_duration ) = @_;
973
    my $base_date = $startdate->clone();
974
975
    $self->{days_mode} ||= q{};
976
977
    if ( $self->{days_mode} eq 'Calendar' ) {
978
        # use the calendar to skip all days the library is closed
979
        # when adding
980
        my $days = abs $days_duration->in_units('days');
981
982
        if ( $days_duration->is_negative() ) {
983
            while ($days) {
984
                $base_date = $self->prev_open_day($base_date);
985
                --$days;
986
            }
987
        } else {
988
            while ($days) {
989
                $base_date = $self->next_open_day($base_date);
990
                --$days;
991
            }
992
        }
993
994
    } else { # Days or Datedue
995
        # use straight days, then use calendar to push
996
        # the date to the next open day if Datedue
997
        $base_date->add_duration($days_duration);
998
999
        if ( $self->{days_mode} eq 'Datedue' ) {
1000
            # Datedue, then use the calendar to push
1001
            # the date to the next open day if holiday
1002
            if (!$self->isOpened($base_date) ) {
1003
1004
                if ( $days_duration->is_negative() ) {
1005
                    $base_date = $self->prev_open_day($base_date);
1006
                } else {
1007
                    $base_date = $self->next_open_day($base_date);
1008
                }
1009
            }
1010
        }
1011
    }
1012
1013
    return $base_date;
1014
}
1015
1016
1;
(-)a/Koha/Schema/Result/DiscreteCalendar.pm (+111 lines)
Line 0 Link Here
1
use utf8;
2
package Koha::Schema::Result::DiscreteCalendar;
3
4
# Created by DBIx::Class::Schema::Loader
5
# DO NOT MODIFY THE FIRST PART OF THIS FILE
6
7
=head1 NAME
8
9
Koha::Schema::Result::DiscreteCalendar
10
11
=cut
12
13
use strict;
14
use warnings;
15
16
use base 'DBIx::Class::Core';
17
18
=head1 TABLE: C<discrete_calendar>
19
20
=cut
21
22
__PACKAGE__->table("discrete_calendar");
23
24
=head1 ACCESSORS
25
26
=head2 date
27
28
  data_type: 'datetime'
29
  datetime_undef_if_invalid: 1
30
  is_nullable: 0
31
32
=head2 branchcode
33
34
  data_type: 'varchar'
35
  is_nullable: 0
36
  size: 10
37
38
=head2 isopened
39
40
  data_type: 'tinyint'
41
  default_value: 1
42
  is_nullable: 1
43
44
=head2 holidaytype
45
46
  data_type: 'varchar'
47
  default_value: (empty string)
48
  is_nullable: 1
49
  size: 1
50
51
=head2 note
52
53
  data_type: 'varchar'
54
  default_value: (empty string)
55
  is_nullable: 1
56
  size: 30
57
58
=head2 openhour
59
60
  data_type: 'time'
61
  is_nullable: 0
62
63
=head2 closehour
64
65
  data_type: 'time'
66
  is_nullable: 0
67
68
=cut
69
70
__PACKAGE__->add_columns(
71
  "date",
72
  {
73
    data_type => "datetime",
74
    datetime_undef_if_invalid => 1,
75
    is_nullable => 0,
76
  },
77
  "branchcode",
78
  { data_type => "varchar", is_nullable => 0, size => 10 },
79
  "isopened",
80
  { data_type => "tinyint", default_value => 1, is_nullable => 1 },
81
  "holidaytype",
82
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 1 },
83
  "note",
84
  { data_type => "varchar", default_value => "", is_nullable => 1, size => 30 },
85
  "openhour",
86
  { data_type => "time", is_nullable => 0 },
87
  "closehour",
88
  { data_type => "time", is_nullable => 0 },
89
);
90
91
=head1 PRIMARY KEY
92
93
=over 4
94
95
=item * L</branchcode>
96
97
=item * L</date>
98
99
=back
100
101
=cut
102
103
__PACKAGE__->set_primary_key("branchcode", "date");
104
105
106
# Created by DBIx::Class::Schema::Loader v0.07045 @ 2017-04-19 10:07:41
107
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:wtctW8ZzCkyCZFZmmavFEw
108
109
110
# You can replace this text with custom code or comments, and it will be preserved on regeneration
111
1;
(-)a/installer/data/mysql/atomicupdate/bug_17015_part1_create_discrete_calendar.sql (+14 lines)
Line 0 Link Here
1
-- Bugzilla 17015
2
-- New koha calendar
3
-- Create discrete_calendar table to keep track of library day's information
4
5
CREATE TABLE `discrete_calendar` (
6
    `date` datetime NOT NULL,
7
    `branchcode` varchar(10) NOT NULL,
8
    `isopened` tinyint(1) DEFAULT 1,
9
    `holidaytype` varchar(1) DEFAULT '',
10
    `note` varchar(30) DEFAULT '',
11
    `openhour` time NOT NULL,
12
    `closehour` time NOT NULL,
13
    PRIMARY KEY (`branchcode`,`date`)
14
);
(-)a/installer/data/mysql/atomicupdate/bug_17015_part2_fill_discrete_calendar.perl (+168 lines)
Line 0 Link Here
1
#!/usr/bin/perl
2
3
#
4
#   Script that fills the discrete_calendar table with dates, using the other date-related tables
5
#
6
use strict;
7
use warnings;
8
use DateTime;
9
use DateTime::Format::Strptime;
10
use Getopt::Long;
11
use C4::Context;
12
13
# Options
14
my $help = 0;
15
my $daysInFuture = 365;
16
GetOptions (
17
            'days|?|d=i' => \$daysInFuture,
18
            'help|?|h' => \$help);
19
my $usage = << 'ENDUSAGE';
20
21
Script that manages the discrete_calendar table.
22
23
This script has the following parameters :
24
    --days --d : how many days in the future will be created, by default it's 365
25
    -h --help: this message
26
    --generate: fills discrete_calendar table with dates from the last two years and the next one
27
28
ENDUSAGE
29
30
if ($help) {
31
    print $usage;
32
    exit;
33
}
34
my $dbh = C4::Context->dbh;
35
$dbh->{AutoCommit} = 0;
36
$dbh->{RaiseError} = 1;
37
38
my $currentDate = DateTime->today;
39
40
# two years ago
41
my $startDate = DateTime->new(
42
day       => $currentDate->day(),
43
month     => $currentDate->month(),
44
year      => $currentDate->year()-2,
45
time_zone => C4::Context->tz()
46
)->truncate( to => 'day' );
47
48
# a year into the future
49
my $endDate = DateTime->new(
50
day       => $currentDate->day(),
51
month     => $currentDate->month(),
52
year      => $currentDate->year(),
53
time_zone => C4::Context->tz()
54
)->truncate( to => 'day' );
55
$endDate->add(days => $daysInFuture);
56
57
#Added a default (standard) branch.
58
my $add_default_branch = 'INSERT IGNORE INTO branches (branchname, branchcode) VALUES(?,?)';
59
my $add_Branch_Sth = $dbh->prepare($add_default_branch);
60
$add_Branch_Sth->execute('Default', 'DFLT');
61
# finds branches;
62
my $selectBranchesSt = 'SELECT branchcode FROM branches';
63
my $selectBranchesSth = $dbh->prepare($selectBranchesSt);
64
$selectBranchesSth->execute();
65
my @branches = ();
66
while ( my $branchCode = $selectBranchesSth->fetchrow_array ) {
67
68
    push @branches,$branchCode;
69
}
70
71
# finds what days are closed for each branch
72
my %repeatableHolidaysPerBranch = ();
73
my %specialHolidaysPerBranch = ();
74
my $selectWeeklySt;
75
my $selectWeeklySth;
76
77
foreach my $branch (@branches){
78
79
    $selectWeeklySt = 'SELECT weekday, title, day, month FROM repeatable_holidays WHERE branchcode = ?';
80
    $selectWeeklySth = $dbh->prepare($selectWeeklySt);
81
    $selectWeeklySth->execute($branch);
82
83
    my @weeklyHolidays = ();
84
85
    while ( my ($weekDay, $title, $day, $month) = $selectWeeklySth->fetchrow_array ) {
86
        push @weeklyHolidays,{weekday => $weekDay, title => $title, day => $day, month => $month};
87
88
    }
89
90
    $repeatableHolidaysPerBranch{$branch} = \@weeklyHolidays;
91
92
    my $selectSpecialHolidayDateSt = 'SELECT day,month,year,title FROM special_holidays WHERE branchcode = ? AND isexception = 0';
93
    my $specialHolidayDatesSth = $dbh->prepare($selectSpecialHolidayDateSt);
94
    $specialHolidayDatesSth -> execute($branch);
95
    # Tranforms dates from specialHolidays table in DateTime for our new table
96
    my @specialHolidayDates = ();
97
    while ( my ($day, $month, $year, $title) = $specialHolidayDatesSth->fetchrow_array ) {
98
99
        my $specialHolidayDate = DateTime->new(
100
        day       => $day,
101
        month     => $month,
102
        year      => $year,
103
        time_zone => C4::Context->tz()
104
        )->truncate( to => 'day' );
105
        push @specialHolidayDates,{date=>$specialHolidayDate, title=> $title};
106
    }
107
108
    $specialHolidaysPerBranch{$branch} = \@specialHolidayDates;
109
}
110
# Fills table with dates and sets 'isopened' according to the branch's weekly restrictions (repeatable_holidays)
111
my $insertDateSt;
112
my $insertDateSth;
113
114
# Loop that does everything in the world
115
for (my $tempDate = $startDate->clone(); $tempDate <= $endDate;$tempDate->add(days => 1)){
116
    foreach my $branch (@branches){
117
        my $dayOfWeek = $tempDate->day_of_week;
118
        # Representation fix
119
        # DateTime object dow (1-7) where Monday is 1
120
        # Arrays are 0-based where 0 = Sunday, not 7.
121
        $dayOfWeek -=1 unless $dayOfWeek ==7;
122
        $dayOfWeek =0 if $dayOfWeek ==7;
123
124
        my $openhour = "09:00:00";
125
        my $closehour = "17:00:00";
126
127
        # Finds closed days
128
        my $isOpened =1;
129
        my $specialDescription = "";
130
        my $holidaytype ='';
131
        $dayOfWeek = $tempDate->day_of_week;
132
        foreach my $holidayWeekDay (@{$repeatableHolidaysPerBranch{$branch}}){
133
            if($holidayWeekDay->{weekday} && $dayOfWeek == $holidayWeekDay->{weekday}){
134
                $isOpened = 0;
135
                $specialDescription = $holidayWeekDay->{title};
136
                $holidaytype = 'W';
137
            }elsif($holidayWeekDay->{day} && $holidayWeekDay->{month}){
138
                my $date = DateTime->new(
139
                day       => $holidayWeekDay->{day},
140
                month     => $holidayWeekDay->{month},
141
                year      => $tempDate->year(),
142
                time_zone => C4::Context->tz()
143
                )->truncate( to => 'day' );
144
145
                if ($tempDate == $date) {
146
                    $isOpened = 0;
147
                    $specialDescription = $holidayWeekDay->{title};
148
                    $holidaytype = 'R';
149
                }
150
            }
151
        }
152
153
        foreach my $specialDate (@{$specialHolidaysPerBranch{$branch}}){
154
            if($tempDate->datetime() eq $specialDate->{date}->datetime() ){
155
                $isOpened = 0;
156
                $specialDescription = $specialDate->{title};
157
                $holidaytype = 'E';
158
            }
159
        }
160
        #final insert statement
161
162
        $insertDateSt = 'INSERT INTO discrete_calendar (date,branchcode,isopened,holidaytype,note,openhour,closehour) VALUES (?,?,?,?,?,?,?)';
163
        $insertDateSth = $dbh->prepare($insertDateSt);
164
        $insertDateSth->execute($tempDate,$branch,$isOpened,$holidaytype,$specialDescription,$openhour,$closehour);
165
    }
166
}
167
# If everything went well we commit to the database
168
$dbh->commit();
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/discrete_calendar.tt (+787 lines)
Line 0 Link Here
1
[% USE Branches %]
2
[% INCLUDE 'doc-head-open.inc' %]
3
<title>Koha &rsaquo; Tools &rsaquo; [% Branches.GetName( branch ) %] calendar</title>
4
[% INCLUDE 'doc-head-close.inc' %]
5
[% INCLUDE 'calendar.inc' %]
6
<link rel="stylesheet" type="text/css" href="[% interface %]/[% theme %]/css/datatables.css" />
7
<script type="text/javascript" src="[% interface %]/lib/jquery/plugins/jquery-ui-timepicker-addon.min.js"></script>
8
[% INCLUDE 'datatables.inc' %]
9
    <script type="text/javascript">
10
    //<![CDATA[
11
        var weekdays = new Array(_("Sundays"),_("Mondays"),_("Tuesdays"),_("Wednesdays"),_("Thursdays"),_("Fridays"),_("Saturdays"));
12
        // Array containing all the information about each date in the calendar.
13
        var datesInfos = new Array();
14
        [% FOREACH date IN datesInfos %]
15
            datesInfos["[% date.date %]"] = {
16
                title : "[% date.note %]",
17
                outputdate : "[% date.outputdate %]",
18
                holidaytype:"[% date.holidaytype %]",
19
                openhour: "[% date.openhour %]",
20
                closehour: "[% date.closehour %]"
21
            };
22
        [% END %]
23
24
        /**
25
        * Displays the details of the selected date on a side panel
26
        */
27
        function showHoliday (date_obj, dateString, dayName, day, month, year, weekDay, title, holidayType) {
28
            $("#newHoliday").slideDown("fast");
29
            $("#copyHoliday").slideUp("fast");
30
            $('#newDaynameOutput').html(dayName);
31
            $('#newDayname').val(dayName);
32
            $('#newBranchNameOutput').html($("#branch :selected").text());
33
            $(".newHoliday ,#branch").val($('#branch').val());
34
            $('#newDayOutput').html(day);
35
            $(".newHoliday #Day").val(day);
36
            $(".newHoliday #Month").val(month);
37
            $(".newHoliday #Year").val(year);
38
            $("#newMonthOutput").html(month);
39
            $("#newYearOutput").html(year);
40
            $(".newHoliday, #Weekday").val(weekDay);
41
42
            $('.newHoliday #title').val(title);
43
            $('#HolidayType').val(holidayType);
44
            $('#days_of_week option[value="'+ (weekDay + 1)  +'"]').attr('selected', true);
45
            $('#openHour').val(datesInfos[dateString].openhour);
46
            $('#closeHour').val(datesInfos[dateString].closehour);
47
            $('#local_today').val(getSeparetedDate(new Date()).dateString);
48
49
            //This changes the label of the date type on the edit panel
50
            if(holidayType == 'W') {
51
                $("#holtype").attr("class","key repeatableweekly").html(_("Holiday repeating weekly"));
52
            } else if(holidayType == 'R') {
53
                $("#holtype").attr("class","key repeatableyearly").html(_("Holiday repeating yearly"));
54
            } else if(holidayType == 'F') {
55
                $("#holtype").attr("class","key float").html(_("Floating holiday"));
56
            } else if(holidayType == 'N') {
57
                $("#holtype").attr("class","key exception").html(_("Needs validation"));
58
            } else if(holidayType == 'E') {
59
                $("#holtype").attr("class","key holiday").html(_("Unique holiday"));
60
            } else{
61
                $("#holtype").attr("class","key normalday").html(_("Working day "));
62
            }
63
64
            //Select the correct holiday type on the dropdown menu
65
            if (datesInfos[dateString].holidaytype !=''){
66
                var type = datesInfos[dateString].holidaytype;
67
                $('#holidayType option[value="'+ type +'"]').attr('selected', true)
68
            }else{
69
                $('#holidayType option[value="none"]').attr('selected', true)
70
            }
71
72
            //If it is a weekly or repeatable holiday show the option to delete the type
73
            if(datesInfos[dateString].holidaytype == 'W' || datesInfos[dateString].holidaytype == 'R'){
74
                $('#deleteType').show("fast");
75
            }else{
76
                $('#deleteType').hide("fast");
77
            }
78
79
            //This value is to disable and hide input when the date is in the past, because you can't edit it.
80
            var value = false;
81
            var today = new Date();
82
            today.setHours(0,0,0,0);
83
            if(date_obj < today ){
84
                $("#holtype").attr("class","key past-date").html(_("Past date"));
85
                $("#CopyRadioButton").attr("checked", "checked");
86
                value = true;
87
                $(".CopyDatePanel").toggle(value);
88
            }
89
            $("#title").prop('disabled', value);
90
            $("#holidayType select").prop('disabled', value);
91
            $("#openHour").prop('disabled', value);
92
            $("#closeHour").prop('disabled', value);
93
            $("#EditRadioButton").parent().toggle(!value);
94
95
        }
96
97
        function hidePanel(aPanelName) {
98
            $("#"+aPanelName).slideUp("fast");
99
        }
100
101
        function changeBranch () {
102
            var branch = $("#branch option:selected").val();
103
            location.href='/cgi-bin/koha/tools/discrete_calendar.pl?branch=' + branch + '&calendardate=' + "[% calendardate %]";
104
        }
105
106
        function Help() {
107
            newin=window.open("/cgi-bin/koha/help.pl","KohaHelp",'width=600,height=600,toolbar=false,scrollbars=yes');
108
        }
109
110
        // This function gives css clases to each kind of day
111
        function dateStatusHandler(date) {
112
            date = getSeparetedDate(date);
113
            var day = date.day;
114
            var month = date.month;
115
            var year = date.year;
116
            var weekDay = date.weekDay;
117
            var dayName = weekdays[weekDay];
118
            var dateString = date.dateString;
119
            var today = new Date();
120
            today.setHours(0,0,0,0);
121
122
            if (datesInfos[dateString] && datesInfos[dateString].holidaytype =='W'){
123
                return [true, "repeatableweekly", _("Weekly holiday: %s").format(datesInfos[dateString].title)];
124
            } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'R') {
125
                return [true, "repeatableyearly", _("Yearly holiday: %s").format(datesInfos[dateString].title)];
126
            } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'N') {
127
                return [true, "exception", _("Need validation: %s").format(datesInfos[dateString].title)];
128
            } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'F') {
129
                return [true, "float", _("Floating holiday: %s").format(datesInfos[dateString].title)];
130
            } else if (datesInfos[dateString] && datesInfos[dateString].holidaytype == 'E') {
131
                return [true, "holiday", _("Single holiday: %s").format(datesInfos[dateString].title)];
132
            } else {
133
                if(date.date_obj < today ){
134
                    return [true, "past-date", _("Past day")];
135
                }else{
136
                    return [true, "normalday", _("Normal day")];
137
                }
138
            }
139
        }
140
141
        /* This function is in charge of showing the correct panel considering the kind of holiday */
142
        function dateChanged(text, date) {
143
            date = getSeparetedDate(date);
144
            var day = date.day;
145
            var month = date.month;
146
            var year = date.year;
147
            var weekDay = date.weekDay;
148
            var dayName = weekdays[weekDay];
149
            var dateString = date.dateString;
150
            var date_obj = date.date_obj;
151
            //set value of form hidden field
152
            $('#from_copyFrom').val(text);
153
154
            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holidaytype);
155
        }
156
157
        /**
158
        * This function seperate a given date object a returns an array containing all needed information about the date.
159
        */
160
        function getSeparetedDate(date){
161
            var mydate = new Array();
162
            var day = (date.getDate() < 10 ? '0' : '') + date.getDate();
163
            var month = ((date.getMonth()+1) < 10 ? '0' : '') + (date.getMonth() +1);
164
            var year = date.getFullYear();
165
            var weekDay = date.getDay();
166
            //iso date string
167
            var dateString = year + '-' + month + '-' + day;
168
            mydate = {
169
                date_obj : date,
170
                dateString : dateString,
171
                weekDay: weekDay,
172
                year: year,
173
                month: month,
174
                day: day
175
            };
176
177
            return mydate;
178
        }
179
180
        /**
181
        * Valide the forms before send them to the backend
182
        */
183
        function validateForm(form){
184
            if(form =='newHoliday' && $('#CopyRadioButton').is(':checked')){
185
                if($('#to_copyFromDatePicker').val() =='' || $('#to_copyToDatePicker').val() ==''){
186
                    alert("You have to pick a FROM and TO in the Copy to different dates.");
187
                    return false;
188
                }else if ($('#from_copyToDatePicker').val()){
189
                    var from_DateFrom = new Date($("#jcalendar-container").datepicker("getDate"));
190
                    var from_DateTo = new Date($('#from_copyToDatePicker').datepicker("getDate"));
191
                    var to_DateFrom = new Date($('#to_copyFromDatePicker').datepicker("getDate"));
192
                    var to_DateTo = new Date($('#to_copyToDatePicker').datepicker("getDate"));
193
194
                    var from_start = Math.round( from_DateFrom.getTime() / (3600*24*1000)); //days as integer from..
195
                    var from_end   = Math.round( from_DateTo.getTime() / (3600*24*1000));
196
                    var to_start = Math.round( to_DateFrom.getTime() / (3600*24*1000));
197
                    var to_end   = Math.round( to_DateTo.getTime() / (3600*24*1000));
198
199
                    var from_daysDiff = from_end - from_start +1;
200
                    var to_daysDiff = to_end - to_start + 1;
201
                    if(from_daysDiff == to_daysDiff){
202
                        $('#daysnumber').val(to_daysDiff);
203
                        return true;
204
                    }else{
205
                        alert("You have to pick the same number of days if you choose 2 ranges");
206
                        return false;
207
                    }
208
                }
209
            }else if(form == 'CopyCalendar'){
210
                if ($('#newBranch').val() ==''){
211
                    alert("Please select a copy to calendar.");
212
                    return false;
213
                }else{
214
                    return true;
215
                }
216
            }else {
217
                return true;
218
            }
219
        }
220
221
        function go_to_date(isoDate){
222
            //I added the time to get around the timezone
223
            var date = getSeparetedDate(new Date(isoDate + " 00:00:00"));
224
            var day = date.day;
225
            var month = date.month;
226
            var year = date.year;
227
            var weekDay = date.weekDay;
228
            var dayName = weekdays[weekDay];
229
            var dateString = date.dateString;
230
            var date_obj = date.date_obj;
231
232
            $("#jcalendar-container").datepicker("setDate", date_obj);
233
            showHoliday(date_obj, dateString, dayName, day, month, year, weekDay, datesInfos[dateString].title, datesInfos[dateString].holidaytype);
234
        }
235
236
        /**
237
        *Check if date range have the same opening, closing hours and holiday type if there's one.
238
        */
239
        function checkRange(date){
240
            date = new Date(date);
241
            $('#toDate').val(getSeparetedDate(date).dateString);
242
            var fromDate = new Date($("#jcalendar-container").datepicker("getDate"));
243
            var sameHoliday =true;
244
            var sameOpenHours =true;
245
            var sameCloseHours =true;
246
247
            $('#days_of_week option[value="everyday"]').attr('selected', true);
248
            for (var i = fromDate; i <= date ; i.setDate(i.getDate() + 1)) {
249
                var myDate1 = getSeparetedDate(i);
250
                var date1 = myDate1.dateString;
251
                var holidayType1 = datesInfos[date1].holidaytype;
252
                var openhours1 = datesInfos[date1].openhour;
253
                var closehours1 = datesInfos[date1].closehour;
254
                for (var j = fromDate; j <= date ; j.setDate(j.getDate() + 1)) {
255
                    var myDate2 = getSeparetedDate(j);
256
                    var date2 = myDate2.dateString;
257
                    var holidayType2 = datesInfos[date2].holidaytype;
258
                    var openhours2 = datesInfos[date2].openhour;
259
                    var closehours2 = datesInfos[date2].closehour;
260
261
                    if (sameHoliday && holidayType1 != holidayType2){
262
                        $('#holidayType option[value="empty"]').attr('selected', true);
263
                        sameHoliday=false;
264
                    }
265
                    if(sameOpenHours && (openhours1 != openhours2)){
266
                        $('#openHour').val('');
267
                        sameOpenHours=false;
268
                    }
269
                    if(sameCloseHours && (closehours1 != closehours2)){
270
                        $('#closeHour').val('');
271
                        sameCloseHours=false;
272
                    }
273
                }
274
                if (!sameOpenHours && !sameCloseHours && !sameHoliday){
275
                    return false;
276
                }
277
            }
278
            return true;
279
        }
280
281
        $(document).ready(function() {
282
            $(".hint").hide();
283
            $("#branch").change(function(){
284
                changeBranch();
285
            });
286
            $("#holidayweeklyrepeatable>tbody>tr").each(function(){
287
                var first_td = $(this).find('td').first();
288
                first_td.html(weekdays[first_td.html()]);
289
            });
290
            $("a.helptext").click(function(){
291
                $(this).parent().find(".hint").toggle(); return false;
292
            });
293
            //Set the correct coloring, default date and the date ranges for all datepickers
294
            $.datepicker.setDefaults({
295
                beforeShowDay: function(thedate) {
296
                    return dateStatusHandler(thedate);
297
                },
298
                defaultDate: new Date("[% keydate %]"),
299
                minDate: new Date("[% minDate %]"),
300
                maxDate: new Date("[% maxDate %]")
301
            });
302
            //Main datepicker
303
            $("#jcalendar-container").datepicker({
304
                onSelect: function(dateText, inst) {
305
                    dateChanged(dateText, $(this).datepicker("getDate"));
306
                },
307
            });
308
            $('#from_copyToDatePicker').datepicker();
309
            $("#from_copyToDatePicker").change(function(){
310
                checkRange($(this).datepicker("getDate"));
311
                $('#from_copyTo').val(($(this).val()));
312
                if($('#from_copyToDatePicker').val()){
313
                    $('#days_of_week').show("fast");
314
                }else{
315
                    $('#days_of_week').hide("fast");
316
                }
317
            });
318
            //Datepickers for copy dates feature
319
            $('#to_copyFromDatePicker').datepicker();
320
            $("#to_copyFromDatePicker").change(function(){
321
                $('#to_copyFrom').val(($(this).val()));
322
            });
323
            $('#to_copyToDatePicker').datepicker();
324
            $("#to_copyToDatePicker").change(function(){
325
                $('#to_copyTo').val(($(this).val()));
326
            });
327
            //Timepickers for open and close hours
328
            $('#openHour').timepicker({
329
                showOn : 'focus',
330
                timeFormat: 'HH:mm:ss',
331
                showSecond: false,
332
                stepMinute: 5,
333
            });
334
            $('#closeHour').timepicker({
335
                showOn : 'focus',
336
                timeFormat: 'HH:mm:ss',
337
                showSecond: false,
338
                stepMinute: 5,
339
            });
340
341
            $('.newHoliday input[type="radio"]').click(function () {
342
                if ($(this).attr("id") == "CopyRadioButton") {
343
                    $(".CopyToBranchPanel").hide('fast');
344
                    $(".CopyDatePanel").show('fast');
345
                } else if ($(this).attr("id") == "CopyToBranchRadioButton"){
346
                    $(".CopyDatePanel").hide('fast');
347
                    $(".CopyToBranchPanel").show('fast');
348
                } else{
349
                    $(".CopyDatePanel").hide('fast');
350
                    $(".CopyToBranchPanel").hide('fast');
351
                }
352
            });
353
354
            $(".hidePanel").on("click",function(){
355
                if( $(this).hasClass("showHoliday") ){
356
                    hidePanel("showHoliday");
357
                }if ($(this).hasClass('newHoliday')) {
358
                    hidePanel("newHoliday");
359
                }else {
360
                    hidePanel("copyHoliday");
361
                }
362
            });
363
364
            $("#deleteType_checkbox").on("change", function(){
365
                if($("#deleteType_checkbox").is(':checked')){
366
                    $('#holidayType option[value="none"]').attr('selected', true);
367
                }
368
            });
369
            $("#holidayType select").on("change", function(){
370
                if($("#holidayType select").val() == "R"){
371
                    $('#days_of_week').hide("fast");
372
                }else if ($('#from_copyToDatePicker').val()){
373
                    $('#days_of_week').show("fast");
374
                }
375
            });
376
        });
377
    //]]>
378
    </script>
379
    <!-- Datepicker colors -->
380
    <style type="text/css">
381
        #jcalendar-container .ui-datepicker {
382
            font-size : 185%;
383
        }
384
        #holidayweeklyrepeatable, #holidaysyearlyrepeatable, #holidaysunique, #holidayexceptions {
385
            font-size : 90%; margin-bottom : 1em;
386
        }
387
        #showHoliday {
388
            margin : .5em 0;
389
        }
390
        .key {
391
            padding : 3px;
392
            white-space:nowrap;
393
            line-height:230%;
394
        }
395
        .ui-datepicker {
396
            font-size : 150%;
397
        }
398
        .ui-datepicker th, .ui-datepicker .ui-datepicker-title select {
399
            font-size : 80%;
400
        }
401
        .ui-datepicker td a {
402
            padding : .5em;
403
        }
404
        .ui-datepicker td span {
405
            padding : .5em;
406
            border : 1px solid #BCBCBC;
407
        }
408
        .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
409
            font-size : 80%;
410
        }
411
        .key {
412
            padding : 3px; white-space:nowrap; line-height:230%;
413
        }
414
        .normalday {
415
            background-color :  #EDEDED;
416
            color :  Black;
417
            border : 1px solid #BCBCBC;
418
        }
419
        .ui-datepicker-unselectable {
420
            padding :.5em; white-space:nowrap;
421
        }
422
        .ui-state-disabled {
423
            padding :.5em; white-space:nowrap;
424
        }
425
        .exception {
426
            background-color :  #b3d4ff; color :  Black; border : 1px solid #BCBCBC;
427
        }
428
        .past-date {
429
            background-color :  #e6e6e6; color :  #555555; border : 1px solid #BCBCBC;
430
        }
431
        td.past-date a.ui-state-default {
432
            background : #e6e6e6 ; color :  #555555;
433
        }
434
        .float {
435
            background-color :  #66ff33; color :  Black; border : 1px solid #BCBCBC;
436
        }
437
        .holiday {
438
            background-color :  #ffaeae; color :  Black;  border : 1px solid #BCBCBC;
439
        }
440
        .repeatableweekly {
441
            background-color :  #FFFF99; color :  Black;  border : 1px solid #BCBCBC;
442
        }
443
        .repeatableyearly {
444
            background-color :  #FFCC66; color :  Black;  border : 1px solid #BCBCBC;
445
        }
446
        td.exception a.ui-state-default {
447
            background:  #b3d4ff none; color :  Black; border : 1px solid #BCBCBC;
448
        }
449
        td.float a.ui-state-default {
450
            background:  #66ff33 none; color :  Black; border : 1px solid #BCBCBC;
451
        }
452
        td.holiday a.ui-state-default {
453
            background:  #ffaeae none; color :  Black;  border : 1px solid #BCBCBC;
454
        }
455
        td.repeatableweekly a.ui-state-default {
456
            background:  #FFFF99 none; color :  Black;  border : 1px solid #BCBCBC;
457
        }
458
        td.repeatableyearly a.ui-state-default {
459
            background:  #FFCC66 none; color :  Black;  border : 1px solid #BCBCBC;
460
        }
461
        .information {
462
            z-index : ; background-color :  #DCD2F1; width : 300px; display : none; border : 1px solid #000000; color :  #000000; font-size :  8pt; font-weight :  bold; background-color :  #FFD700; cursor :  pointer; padding : 2px;
463
        }
464
        .panel {
465
            z-index : 1; display : none; border : 3px solid #CCC; padding : 3px; margin-top: .3em;  background-color: #FEFEFE;
466
        }
467
        fieldset.brief {
468
            border : 0; margin-top: 0;
469
        }
470
        h1 select {
471
            width: 20em;
472
        }
473
        div.yui-b fieldset.brief ol {
474
            font-size:100%;
475
        }
476
        div.yui-b fieldset.brief li, div.yui-b fieldset.brief li.radio  {
477
            padding:0.2em 0;
478
        }
479
        .help {
480
            margin:.3em 0;border:1px solid #EEE;padding:.3em .7em; font-size : 90%;
481
        }
482
        .calendar td, .calendar th, .calendar .button, .calendar tbody .day {
483
            padding : .7em; font-size: 110%;
484
        }
485
        .calendar { width: auto; border : 0;
486
        }
487
        .copyHoliday form li{
488
            display:table-row
489
        }
490
        .copyHoliday form li b, .copyHoliday form li input{
491
            display:table-cell; margin-bottom: 2px;
492
        }
493
    </style>
494
</head>
495
496
<body id="tools_holidays" class="tools">
497
[% INCLUDE 'header.inc' %]
498
[% INCLUDE 'cat-search.inc' %]
499
500
<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>
501
502
<div id="doc3" class="yui-t1">
503
504
   <div id="bd">
505
    <div id="yui-main">
506
    <div class="yui-b">
507
    <h2>[% Branches.GetName( branch ) %] calendar</h2>
508
    <div class="yui-g">
509
    <div class="yui-u first" style="width:60%">
510
        <label for="branch">Define the holidays for:</label>
511
        <form method="post" onsubmit="return validateForm('CopyCalendar')">
512
            <select id="branch" name="branch">
513
                [% PROCESS options_for_libraries libraries => Branches.all( selected => branch ) %]
514
            </select>
515
            Copy calendar to
516
            <select id='newBranch' name ='newBranch'>
517
                <option value=""></option>
518
                [% FOREACH l IN Branches.all() %]
519
                    [% UNLESS branch == l.branchcode %]
520
                    <option value="[% l.branchcode %]">[% l.branchname %]</option>
521
                    [% END %]
522
                [% END %]
523
            </select>
524
            <input type="hidden" name="action" value="copyBranch" />
525
            <input type="submit" value="Clone">
526
        </form>
527
            <h3>Calendar information</h3>
528
            <div id="jcalendar-container" style="float: left"></div>
529
    <!-- ***************************** Panel to deal with new holidays **********************  -->
530
    [% UNLESS  datesInfos %]
531
    <div class="alert alert-danger" style="float: left; margin-left:15px">
532
        <strong>Error!</strong> You have to run generate_discrete_calendar.pl in order to use Discrete Calendar.
533
    </div>
534
    [% END %]
535
536
    [% IF  no_branch_selected %]
537
    <div class="alert alert-danger" style="float: left; margin-left:15px">
538
        <strong>No library set!</strong> You are using the default calendar.
539
    </div>
540
    [% END %]
541
542
    <div class="panel newHoliday" id="newHoliday" style="float: left; margin-left:15px">
543
        <form method="post" onsubmit="return validateForm('newHoliday')">
544
            <fieldset class="brief">
545
                <h3>Edit date details</h3>
546
                <span id="holtype"></span>
547
                <ol>
548
                    <li>
549
                        <strong>Library:</strong>
550
                        <span id="newBranchNameOutput"></span>
551
                        <input type="hidden" id="branch" name="branch" />
552
                    </li>
553
                    <li>
554
                        <strong>From date:</strong>
555
                        <span id="newDaynameOutput"></span>,
556
557
                        [% 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 %]
558
559
                        <input type="hidden" id="newDayname" name="showDayname" />
560
                        <input type="hidden" id="Day" name="Day" />
561
                        <input type="hidden" id="Month" name="Month" />
562
                        <input type="hidden" id="Year" name="Year" />
563
                    </li>
564
                    <li class="dateinsert">
565
                        <b>To date: </b>
566
                        <input type="text" id="from_copyToDatePicker" name="toDate" size="20" class="datepicker" />
567
                    </li>
568
                    <li>
569
                        <label for="title">Title: </label><input type="text" name="Title" id="title" size="35" />
570
                    </li>
571
                    <li id="holidayType">
572
                        <label for="holidayType">Date type</label>
573
                        <select name ='holidayType'>
574
                            <option value="empty"></option>
575
                            <option value="none">Working day</option>
576
                            <option value="E">Unique holiday</option>
577
                            <option value="W">Weekly holiday</option>
578
                            <option value="R">Repeatable holiday</option>
579
                            <option value="F">Floating holiday</option>
580
                            <option value="N" disabled>Need validation</option>
581
                        </select>
582
                    </li>
583
                    <li id="days_of_week" style="display :none">
584
                        <label for="day_of_week">Week day</label>
585
                        <select name ='day_of_week'>
586
                            <option value="everyday">Everyday</option>
587
                            <option value="1">Sundays</option>
588
                            <option value="2">Mondays</option>
589
                            <option value="3">Tuesdays</option>
590
                            <option value="4">Wednesdays</option>
591
                            <option value="5">Thursdays</option>
592
                            <option value="6">Fridays</option>
593
                            <option value="7">Saturdays</option>
594
                        </select>
595
                    </li>
596
                    <li class="radio" id="deleteType" style="display : none;" >
597
                        <input type="checkbox" name="deleteType" id="deleteType_checkbox" value="1" ><label for="deleteType_checkbox"> Delete this type</label>
598
                        <a href="#" class="helptext">[?]</a>
599
                        <div class="hint">Remove all repeated or weekly holidays of the selected date or week day <br> if working day is selected.</div>
600
                    </li>
601
                    <li>
602
                        <label for="openHour">Open hours: </label><input type="text" name="openHour" id='openHour' style="display :flex"  >
603
                    </li>
604
                    <li>
605
                        <label for="closeHour">Close hours: </label><input type="text" name="closeHour" id='closeHour' style="display :flex" >
606
                    </li>
607
                    <li class="radio">
608
                        <input type="radio" name="action" id="EditRadioButton" value="edit" checked/>
609
                        <label for="EditRadioButton">Edit selected dates</label>
610
                    </li>
611
                    <li class="radio">
612
                        <input type="radio" name="action" id="CopyRadioButton" value="copyDates" />
613
                        <label for="CopyRadioButton">Copy to different dates</label>
614
                        <div class="CopyDatePanel" style="display:none; padding-left:15px">
615
                            <b>From : </b>
616
                            <input type="text" id="to_copyFromDatePicker" size="20" class="datepicker"/>
617
                            <b>To : </b>
618
                            <input type="text" id="to_copyToDatePicker" size="20" class="datepicker"/>
619
                        </div>
620
                        <input type="hidden" name="daysnumber" id='daysnumber'>
621
                        <!-- These  yyyy-mm-dd -->
622
                        <input type="hidden" name="from_copyFrom" id='from_copyFrom'>
623
                        <input type="hidden" name="from_copyTo" id='from_copyTo'>
624
                        <input type="hidden" name="to_copyFrom" id='to_copyFrom'>
625
                        <input type="hidden" name="to_copyTo" id='to_copyTo'>
626
                        <input type="hidden" name="local_today" id='local_today'>
627
                    </li>
628
                </ol>
629
                <fieldset class="action">
630
                    <input type="submit" name="submit" value="Save" />
631
                    <a href="#" class="cancel hidePanel newHoliday">Cancel</a>
632
                </fieldset>
633
            </fieldset>
634
        </form>
635
    </div>
636
637
<!-- ************************************************************************************** -->
638
<!-- ******                              MAIN SCREEN CODE                            ****** -->
639
<!-- ************************************************************************************** -->
640
641
</div>
642
<div class="yui-u" style="width : 40%">
643
    <div class="help">
644
        <h4>Hints</h4>
645
        <ul>
646
            <li>Search in the calendar the day you want to set as holiday.</li>
647
            <li>Click the date to add or edit a holiday.</li>
648
            <li>Specify how the holiday should repeat.</li>
649
            <li>Click Save to finish.</li>
650
            <li>PS:
651
                <ul>
652
                    <li>You can't edit passed dates</li>
653
                    <li>Weekly holidays change open/close hours for all the days affected unless inputs are empty</li>
654
                </ul>
655
            </li>
656
        </ul>
657
        <h4>Key</h4>
658
        <p>
659
            <span class="key normalday">Working day </span>
660
            <span class="key holiday">Unique holiday</span>
661
            <span class="key repeatableweekly">Holiday repeating weekly</span>
662
            <span class="key repeatableyearly">Holiday repeating yearly</span>
663
            <span class="key float">Floating holiday</span>
664
            <span class="key exception">Need validation</span>
665
        </p>
666
    </div>
667
<div id="holiday-list">
668
669
    [% IF ( NEED_VALIDATION_HOLIDAYS ) %]
670
    <h3>Need validation holidays</h3>
671
    <table id="holidaysunique">
672
        <thead>
673
            <tr>
674
                <th class="exception">Date</th>
675
                <th class="exception">Title</th>
676
            </tr>
677
        </thead>
678
        <tbody>
679
            [% FOREACH need_validation_holiday IN NEED_VALIDATION_HOLIDAYS %]
680
            <tr>
681
                <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>
682
                <td>[% need_validation_holiday.note %]</td>
683
            </tr>
684
            [% END %]
685
        </tbody>
686
    </table>
687
    [% END %]
688
689
    [% IF ( WEEKLY_HOLIDAYS ) %]
690
    <h3>Weekly - Repeatable holidays</h3>
691
    <table id="holidayweeklyrepeatable">
692
        <thead>
693
            <tr>
694
                <th class="repeatableweekly">Day of week</th>
695
                <th class="repeatableweekly">Title</th>
696
            </tr>
697
        </thead>
698
        <tbody>
699
            [% FOREACH WEEK_DAYS_LOO IN WEEKLY_HOLIDAYS %]
700
            <tr>
701
                <td>[% WEEK_DAYS_LOO.weekday %]</td>
702
            </td>
703
            <td>[% WEEK_DAYS_LOO.note %]</td>
704
        </tr>
705
        [% END %]
706
    </tbody>
707
</table>
708
[% END %]
709
710
[% IF ( REPEATABLE_HOLIDAYS ) %]
711
<h3>Yearly - Repeatable holidays</h3>
712
<table id="holidaysyearlyrepeatable">
713
    <thead>
714
        <tr>
715
            [% IF ( dateformat == "metric" ) %]
716
            <th class="repeatableyearly">Day/month</th>
717
            [% ELSE %]
718
            <th class="repeatableyearly">Month/day</th>
719
            [% END %]
720
            <th class="repeatableyearly">Title</th>
721
        </tr>
722
    </thead>
723
    <tbody>
724
        [% FOREACH DAY_MONTH_HOLIDAYS_LOO IN REPEATABLE_HOLIDAYS %]
725
        <tr>
726
            [% IF ( dateformat == "metric" ) %]
727
            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.day %]/[% DAY_MONTH_HOLIDAYS_LOO.month %]</span></td>
728
            [% ELSE %]
729
            <td><span title="[% DAY_MONTH_HOLIDAYS_LOO.DATE_SORT %]">[% DAY_MONTH_HOLIDAYS_LOO.month %]/[% DAY_MONTH_HOLIDAYS_LOO.day %]</span></td>
730
            [% END %]
731
            <td>[% DAY_MONTH_HOLIDAYS_LOO.note %]</td>
732
        </tr>
733
        [% END %]
734
    </tbody>
735
</table>
736
[% END %]
737
738
[% IF ( UNIQUE_HOLIDAYS ) %]
739
<h3>Unique holidays</h3>
740
<table id="holidaysunique">
741
    <thead>
742
        <tr>
743
            <th class="holiday">Date</th>
744
            <th class="holiday">Title</th>
745
        </tr>
746
    </thead>
747
    <tbody>
748
        [% FOREACH HOLIDAYS_LOO IN UNIQUE_HOLIDAYS %]
749
        <tr>
750
            <td><a href="#doc3" onclick="go_to_date('[% HOLIDAYS_LOO.date %]')"><span title="[% HOLIDAYS_LOO.DATE_SORT %]">[% HOLIDAYS_LOO.outputdate %]</span></a></td>
751
            <td>[% HOLIDAYS_LOO.note %]</td>
752
        </tr>
753
        [% END %]
754
    </tbody>
755
</table>
756
[% END %]
757
758
[% IF ( FLOAT_HOLIDAYS ) %]
759
<h3>Floating holidays</h3>
760
<table id="holidaysunique">
761
    <thead>
762
        <tr>
763
            <th class="float">Date</th>
764
            <th class="float">Title</th>
765
        </tr>
766
    </thead>
767
    <tbody>
768
        [% FOREACH float_holiday IN FLOAT_HOLIDAYS %]
769
        <tr>
770
            <td><a href="#doc3" onclick="go_to_date('[% float_holiday.date %]')"><span title="[% float_holiday.DATE_SORT %]">[% float_holiday.outputdate %]</span></a></td>
771
            <td>[% float_holiday.note %]</td>
772
        </tr>
773
        [% END %]
774
    </tbody>
775
</table>
776
[% END %]
777
</div>
778
</div>
779
</div>
780
</div>
781
</div>
782
783
<div class="yui-b noprint">
784
[% INCLUDE 'tools-menu.inc' %]
785
</div>
786
</div>
787
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/misc/cronjobs/add_days_discrete_calendar.pl (+129 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 strict;
7
use warnings;
8
use DateTime;
9
use DateTime::Format::Strptime;
10
use Getopt::Long;
11
use C4::Context;
12
13
# Options
14
my $help = 0;
15
my $daysInFuture = 1;
16
my $debug = 0;
17
GetOptions (
18
    'help|?|h'  => \$help,
19
    'n=i'       => \$daysInFuture,
20
    'd|?|debug' => \$debug);
21
22
my $usage = << 'ENDUSAGE';
23
24
This script adds days into discrete_calendar table based on the same day from the week before.
25
26
Examples :
27
    The latest date on discrete_calendar is : 28-07-2017
28
    The current date : 01-08-2016
29
    The dates that will be added are : 29-07-2017, 30-07-2017, 31-07-2017, 01-08-2017
30
Open close exemples :
31
    Date added is : 29-07-2017
32
    Opening/closing hours will be base on : 22-07-2017 (- 7 days)
33
    Library open or closed will be based on : 29-07-2017 (- 1 year)
34
This script has the following parameters:
35
    -h --help: this message
36
    -n : number of days to add in the futre, default : 1
37
    -d --debug: displays all added days and errors if there is any
38
39
ENDUSAGE
40
41
my $dbh = C4::Context->dbh;
42
$dbh->{AutoCommit} = 0;
43
$dbh->{RaiseError} = 1;
44
my $query = "SELECT distinct weekday(date), note FROM discrete_calendar where holidaytype='W'";
45
my $stmt = $dbh->prepare($query);
46
$stmt->execute();
47
my @week_days_discrete;
48
while (my ($weekday,$note) = $stmt->fetchrow_array){
49
    push @week_days_discrete, {weekday => $weekday, note => $note};
50
}
51
52
if ($help) {
53
    print $usage;
54
    exit;
55
}
56
57
#getting the all the branches
58
my $selectBranchesSt = 'SELECT branchcode FROM discrete_calendar GROUP BY branchcode';
59
my $selectBranchesSth = $dbh->prepare($selectBranchesSt);
60
$selectBranchesSth->execute();
61
my @branches = ();
62
while ( my $branchCode = $selectBranchesSth->fetchrow_array ) {
63
64
    push @branches,$branchCode;
65
}
66
67
#get the latest date in the table
68
$query = "SELECT MAX(date) FROM discrete_calendar";
69
$stmt = $dbh->prepare($query);
70
$stmt->execute();
71
my $latestedDate = $stmt->fetchrow_array;
72
my $parser = DateTime::Format::Strptime->new(
73
    pattern => '%Y-%m-%d %H:%M:%S',
74
    on_error => 'croak',
75
);
76
$latestedDate = $parser->parse_datetime($latestedDate);
77
78
my $newDay = $latestedDate->clone();
79
$latestedDate->add(days => $daysInFuture);
80
81
for ($newDay->add(days => 1);$newDay <= $latestedDate;$newDay->add(days => 1)){
82
    my $lastWeekDay = $newDay->clone();
83
    $lastWeekDay->add(days=> -8);
84
    my $dayOfWeek = $lastWeekDay->day_of_week;
85
    # Representation fix
86
    # DateTime object dow (1-7) where Monday is 1
87
    # Arrays are 0-based where 0 = Sunday, not 7.
88
    $dayOfWeek -= 1 unless $dayOfWeek == 7;
89
    $dayOfWeek = 0 if $dayOfWeek == 7;
90
91
    #checking if it was open on the same day from last year
92
    my $yearAgo = $newDay->clone();
93
    $yearAgo = $yearAgo->add(years => -1);
94
    my $last_year = 'SELECT isopened, holidaytype, note FROM discrete_calendar WHERE date=? AND branchcode=?';
95
    my $day_last_week = "SELECT openhour, closehour FROM discrete_calendar WHERE DAYOFWEEk(date)=DAYOFWEEK(?) and date < ? order by date desc limit 1";
96
    my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,isopened,openhour,closehour) VALUES (?,?,?,?,?)';
97
    my $note ='';
98
    #insert into discrete_calendar for each branch
99
    foreach my $branchCode(@branches){
100
        $stmt = $dbh->prepare($last_year);
101
        $stmt->execute($yearAgo,$branchCode);
102
        my ($isOpened, $holidaytype, $note) = $stmt->fetchrow_array;
103
        #weekly and unique holidays are not replicated in the future
104
        if ($holidaytype && $holidaytype ne "R"){
105
            $isOpened = 1;
106
            if ($holidaytype eq "W" || $holidaytype eq "E"){
107
                $holidaytype='';
108
                $note='';
109
            }elsif ($holidaytype eq "F"){
110
                $holidaytype = 'N';
111
            }
112
        }
113
        $holidaytype = '' if $isOpened;
114
        $stmt = $dbh->prepare($day_last_week);
115
        $stmt->execute($newDay, $newDay);
116
        my ($openhour,$closehour ) = $stmt->fetchrow_array;
117
        my $add_Day = 'INSERT INTO discrete_calendar (date,branchcode,isopened,holidaytype, note,openhour,closehour) VALUES (?,?,?,?,?,?,?)';
118
        $stmt = $dbh->prepare($add_Day);
119
        $stmt->execute($newDay,$branchCode,$isOpened,$holidaytype,$note, $openhour,$closehour);
120
121
        if($debug && !$@){
122
            warn "Added day $newDay to $branchCode is opened : $isOpened, holidaytype : $holidaytype, note: $note, openhour : $openhour, closehour : $closehour \n";
123
        }elsif($@){
124
            warn "Failed to add day $newDay to $branchCode : $_\n";
125
        }
126
    }
127
}
128
# If everything went well we commit to the database
129
$dbh->commit();
(-)a/tools/discrete_calendar.pl (-1 / +149 lines)
Line 0 Link Here
0
- 
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 strict;
20
use warnings;
21
22
use CGI qw ( -utf8 );
23
24
use C4::Auth;
25
use C4::Output;
26
27
use Koha::DateUtils;
28
use Koha::DiscreteCalendar;
29
30
my $input = new CGI;
31
32
# Get the template to use
33
my ($template, $loggedinuser, $cookie)
34
    = get_template_and_user({template_name => "tools/discrete_calendar.tt",
35
                             type => "intranet",
36
                             query => $input,
37
                             authnotrequired => 0,
38
                             flagsrequired => {tools => 'edit_calendar'},
39
                             debug => 1,
40
                           });
41
42
my $branch = $input->param('branch') || C4::Context->userenv->{'branch'};
43
my $calendar = Koha::DiscreteCalendar->new(branchcode => $branch);
44
#alert the user that he is using the default calendar because he does not have a library set
45
my $no_branch_selected = $calendar->{no_branch_selected};
46
47
my $weekday = $input->param('day_of_week');
48
49
my $holidaytype = $input->param('holidayType');
50
my $allbranches = $input->param('allBranches');
51
52
my $title = $input->param('Title');
53
54
my $action = $input->param('action') || '';
55
56
# calendardate - date passed in url for human readability (syspref)
57
# if the url has an invalid date default to 'now.'
58
my $calendarinput_dt = eval { dt_from_string( scalar $input->param('calendardate')); } || dt_from_string;
59
my $calendardate = output_pref( { dt => $calendarinput_dt, dateonly => 1 } );
60
61
if($action eq 'copyBranch'){
62
    $calendar->copyToBranch(scalar $input->param('newBranch'));
63
} elsif($action eq 'copyDates'){
64
    my $from_startDate = $input->param('from_copyFrom') ||'';
65
    my $from_endDate = $input->param('toDate') || '';
66
    my $to_startDate = $input->param('to_copyFrom') || '';
67
    my $to_endDate = $input->param('to_copyTo') || '';
68
    my $daysnumber= $input->param('daysnumber');
69
70
    $from_startDate = dt_from_string(scalar $from_startDate) if$from_startDate  ne '';
71
    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
72
    $to_startDate = dt_from_string(scalar $to_startDate) if $to_startDate ne '';
73
    $to_endDate = dt_from_string(scalar $to_endDate) if $to_endDate ne '';
74
75
    $calendar->copyHoliday($from_startDate, $from_endDate, $to_startDate, $to_endDate, $daysnumber);
76
} elsif($action eq 'edit'){
77
    my $openHour = $input->param('openHour');
78
    my $closeHour = $input->param('closeHour');
79
    my $endDate = $input->param('toDate');
80
    my $deleteType = $input->param('deleteType') || 0;
81
    #Get today from javascript for a precise local time
82
    my $local_today = dt_from_string( $input->param('local_today'), 'iso');
83
84
    my $startDate = dt_from_string(scalar $input->param('from_copyFrom'));
85
86
    if($endDate ne '' ) {
87
        $endDate = dt_from_string(scalar $endDate);
88
    } else{
89
        $endDate = $startDate->clone();
90
    }
91
92
    $calendar->edit_holiday($title, $weekday, $holidaytype, $openHour, $closeHour, $startDate, $endDate, $deleteType, $local_today);
93
}
94
95
# keydate - date passed to calendar.js.  calendar.js does not process dashes within a date.
96
97
my $keydate = output_pref( { dt => $calendarinput_dt, dateonly => 1, dateformat => 'iso' } );
98
$keydate =~ s/-/\//g;
99
100
# Set all the branches.
101
my $onlymine =
102
  (      C4::Context->preference('IndependentBranches')
103
      && C4::Context->userenv
104
      && !C4::Context->IsSuperLibrarian()
105
      && C4::Context->userenv->{branch} ? 1 : 0 );
106
if ( $onlymine ) {
107
    $branch = C4::Context->userenv->{'branch'};
108
}
109
110
# Get all the holidays
111
112
#discrete_calendar weekly holidays
113
my @week_days = $calendar->get_week_days_holidays();
114
115
#discrete_calendar repeatable holidays
116
my @repeatable_holidays = $calendar->get_repeatable_holidays();
117
118
#discrete_calendar unique holidays
119
my @unique_holidays =$calendar->get_unique_holidays();
120
#discrete_calendar floating holidays
121
my @float_holidays =$calendar->get_float_holidays();
122
#discrete_caledar need validation holidays
123
my @need_validation_holidays =$calendar->get_need_valdation_holidays();
124
125
#Calendar maximum date
126
my $minDate = $calendar->getMinDate($branch);
127
128
#Calendar minimum date
129
my $maxDate = $calendar->getMaxDate($branch);
130
131
my @datesInfos = $calendar->getDatesInfo($branch);
132
133
$template->param(
134
    UNIQUE_HOLIDAYS          => \@unique_holidays,
135
    FLOAT_HOLIDAYS           => \@float_holidays,
136
    NEED_VALIDATION_HOLIDAYS => \@need_validation_holidays,
137
    REPEATABLE_HOLIDAYS      => \@repeatable_holidays,
138
    WEEKLY_HOLIDAYS          => \@week_days,
139
    calendardate             => $calendardate,
140
    keydate                  => $keydate,
141
    branch                   => $branch,
142
    minDate                  => $minDate,
143
    maxDate                  => $maxDate,
144
    datesInfos               => \@datesInfos,
145
    no_branch_selected       => $no_branch_selected,
146
);
147
148
# Shows the template with the real values replaced
149
output_html_with_http_headers $input, $cookie, $template->output;

Return to bug 17015