New set of routines for HEAD.
[koha.git] / C4 / Calendar / Calendar.pm
1 package C4::Calendar::Calendar;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
16 # Suite 330, Boston, MA  02111-1307 USA
17
18 use strict;
19 require Exporter;
20 use vars qw($VERSION @EXPORT);
21
22 use C4::Context;
23
24 #use Date::Calc;
25
26 # set the version for version checking
27 $VERSION = 0.01;
28
29 =head1 NAME
30
31 C4::Calendar::Calendar - Koha module dealing with holidays.
32
33 =head1 SYNOPSIS
34
35         use C4::Calendar::Calendar;
36
37 =head1 DESCRIPTION
38
39 This package is used to deal with holidays. Through this package, you can set all kind of holidays for the library.
40
41 =head1 FUNCTIONS
42
43 =over 2
44
45 =cut
46
47 @EXPORT = qw(&new 
48              &change_branchcode 
49                          &get_week_days_holidays 
50                          &get_day_month_holidays 
51              &get_exception_holidays 
52                          &get_single_holidays 
53                          &insert_week_day_holiday 
54                          &insert_day_month_holiday 
55                          &insert_single_holiday 
56                          &insert_exception_holiday
57                          &delete_holiday 
58                          &isHoliday 
59                          &addDate
60                          &daysBetween);
61
62 =item new
63
64         $calendar = C4::Calendar::Calendar->new(branchcode => $branchcode);
65
66 C<$branchcode> Is the branch code wich you want to use calendar.
67
68 =cut
69
70 sub new {
71         my $classname = shift @_;
72         my %options = @_;
73
74         my %hash;
75         my $self = bless(\%hash, $classname);
76
77         foreach my $optionName (keys %options) {
78                 $self->{lc($optionName)} = $options{$optionName};
79         }
80
81         $self->_init;
82
83         return $self;
84 }
85
86 sub _init {
87         my $self = shift @_;
88
89         my $dbh = C4::Context->dbh();
90         my $week_days_sql = $dbh->prepare("select weekday, title, description from repeatable_holidays where ('$self->{branchcode}' = branchcode) and (NOT(ISNULL(weekday)))");
91         $week_days_sql->execute;
92         my %week_days_holidays;
93         while (my ($weekday, $title, $description) = $week_days_sql->fetchrow) {
94                 $week_days_holidays{$weekday}{title} = $title;
95                 $week_days_holidays{$weekday}{description} = $description;
96         }
97         $week_days_sql->finish;
98         $self->{'week_days_holidays'} = \%week_days_holidays;
99
100         my $day_month_sql = $dbh->prepare("select day, month, title, description from repeatable_holidays where ('$self->{branchcode}' = branchcode) and ISNULL(weekday)");
101         $day_month_sql->execute;
102         my %day_month_holidays;
103         while (my ($day, $month, $title, $description) = $day_month_sql->fetchrow) {
104                 $day_month_holidays{"$month/$day"}{title} = $title;
105                 $day_month_holidays{"$month/$day"}{description} = $description;
106         }
107         $day_month_sql->finish;
108         $self->{'day_month_holidays'} = \%day_month_holidays;
109
110         my $exception_holidays_sql = $dbh->prepare("select day, month, year, title, description from special_holidays where ('$self->{branchcode}' = branchcode) and (isexception = 1)");
111         $exception_holidays_sql->execute;
112         my %exception_holidays;
113         while (my ($day, $month, $year, $title, $description) = $exception_holidays_sql->fetchrow) {
114                 $exception_holidays{"$year/$month/$day"}{title} = $title;
115                 $exception_holidays{"$year/$month/$day"}{description} = $description;
116         }
117         $exception_holidays_sql->finish;
118         $self->{'exception_holidays'} = \%exception_holidays;
119
120         my $holidays_sql = $dbh->prepare("select day, month, year, title, description from special_holidays where ('$self->{branchcode}' = branchcode) and (isexception = 0)");
121         $holidays_sql->execute;
122         my %single_holidays;
123         while (my ($day, $month, $year, $title, $description) = $holidays_sql->fetchrow) {
124                 $single_holidays{"$year/$month/$day"}{title} = $title;
125                 $single_holidays{"$year/$month/$day"}{description} = $description;
126         }
127         $holidays_sql->finish;
128         $self->{'single_holidays'} = \%single_holidays;
129 }
130
131 =item change_branchcode
132
133         $calendar->change_branchcode(branchcode => $branchcode)
134
135 Change the calendar branch code. This means to change the holidays structure.
136
137 C<$branchcode> Is the branch code wich you want to use calendar.
138
139 =cut
140
141 sub change_branchcode {
142         my ($self, $branchcode) = @_;
143         my %options = @_;
144
145         foreach my $optionName (keys %options) {
146                 $self->{lc($optionName)} = $options{$optionName};
147         }
148         $self->_init;
149
150         return $self;
151 }
152
153 =item get_week_days_holidays
154
155         $week_days_holidays = $calendar->get_week_days_holidays();
156
157 Returns a hash reference to week days holidays.
158
159 =cut
160
161 sub get_week_days_holidays {
162         my $self = shift @_;
163         my $week_days_holidays = $self->{'week_days_holidays'};
164         return $week_days_holidays;
165 }
166
167 =item get_day_month_holidays
168         
169         $day_month_holidays = $calendar->get_day_month_holidays();
170
171 Returns a hash reference to day month holidays.
172
173 =cut
174
175 sub get_day_month_holidays {
176         my $self = shift @_;
177         my $day_month_holidays = $self->{'day_month_holidays'};
178         return $day_month_holidays;
179 }
180
181 =item get_exception_holidays
182         
183         $exception_holidays = $calendar->exception_holidays();
184
185 Returns a hash reference to exception holidays. This kind of days are those
186 which stands for a holiday, but you wanted to make an exception for this particular
187 date.
188
189 =cut
190
191 sub get_exception_holidays {
192         my $self = shift @_;
193         my $exception_holidays = $self->{'exception_holidays'};
194         return $exception_holidays;
195 }
196
197 =item get_single_holidays
198         
199         $single_holidays = $calendar->get_single_holidays();
200
201 Returns a hash reference to single holidays. This kind of holidays are those which
202 happend just one time.
203
204 =cut
205
206 sub get_single_holidays {
207         my $self = shift @_;
208         my $single_holidays = $self->{'single_holidays'};
209         return $single_holidays;
210 }
211
212 =item insert_week_day_holiday
213
214         insert_week_day_holiday(weekday => $weekday,
215                                                         title => $title,
216                                                         description => $description);
217
218 Inserts a new week day for $self->{branchcode}.
219
220 C<$day> Is the week day to make holiday.
221
222 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
223
224 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
225
226 =cut
227
228 sub insert_week_day_holiday {
229         my $self = shift @_;
230         my %options = @_;
231
232         my $dbh = C4::Context->dbh();
233         my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (id,branchcode,weekday,day,month,title,description) values ('', '$self->{branchcode}', $options{weekday}, NULL, NULL, '$options{title}', '$options{description}')");
234         $insertHoliday->execute;
235         $insertHoliday->finish;
236
237         $self->{'week_days_holidays'}->{$options{weekday}}{title} = $options{title};
238         $self->{'week_days_holidays'}->{$options{weekday}}{description} = $options{description};
239         return $self;
240 }
241
242 =item insert_day_month_holiday
243
244         insert_day_month_holiday(day => $day,
245                                  month => $month,
246                                                          title => $title,
247                                                          description => $description);
248
249 Inserts a new day month holiday for $self->{branchcode}.
250
251 C<$day> Is the day month to make the date to insert.
252
253 C<$month> Is month to make the date to insert.
254
255 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
256
257 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
258
259 =cut
260
261 sub insert_day_month_holiday {
262         my $self = shift @_;
263         my %options = @_;
264
265         my $dbh = C4::Context->dbh();
266         my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (id,branchcode,weekday,day,month,title,description) values ('', '$self->{branchcode}', NULL, $options{day}, $options{month}, '$options{title}', '$options{description}')");
267         $insertHoliday->execute;
268         $insertHoliday->finish;
269
270         $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
271         $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
272         return $self;
273 }
274
275 =item insert_single_holiday
276
277         insert_single_holiday(day => $day,
278                               month => $month,
279                                                   year => $year,
280                                                   title => $title,
281                                                   description => $description);
282
283 Inserts a new single holiday for $self->{branchcode}.
284
285 C<$day> Is the day month to make the date to insert.
286
287 C<$month> Is month to make the date to insert.
288
289 C<$year> Is year to make the date to insert.
290
291 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
292
293 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
294
295 =cut
296
297 sub insert_single_holiday {
298         my $self = shift @_;
299         my %options = @_;
300
301         my $dbh = C4::Context->dbh();
302         my $isexception = 0;
303         my $insertHoliday = $dbh->prepare("insert into special_holidays (id,branchcode,day,month,year,isexception,title,description) values ('', '$self->{branchcode}', $options{day}, $options{month}, $options{year}, $isexception, '$options{title}', '$options{description}')");
304         $insertHoliday->execute;
305         $insertHoliday->finish;
306
307         $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
308         $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
309         return $self;
310 }
311
312 =item insert_exception_holiday
313
314         insert_exception_holiday(day => $day,
315                                  month => $month,
316                                                      year => $year,
317                                                      title => $title,
318                                                      description => $description);
319
320 Inserts a new exception holiday for $self->{branchcode}.
321
322 C<$day> Is the day month to make the date to insert.
323
324 C<$month> Is month to make the date to insert.
325
326 C<$year> Is year to make the date to insert.
327
328 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
329
330 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
331
332 =cut
333
334 sub insert_exception_holiday {
335         my $self = shift @_;
336         my %options = @_;
337
338         my $dbh = C4::Context->dbh();
339         my $isexception = 1;
340         my $insertException = $dbh->prepare("insert into special_holidays (id,branchcode,day,month,year,isexception,title,description) values ('', '$self->{branchcode}', $options{day}, $options{month}, $options{year}, $isexception, '$options{title}', '$options{description}')");
341         $insertException->execute;
342         $insertException->finish;
343
344         $self->{'exceptions_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
345         $self->{'exceptions_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
346         return $self;
347 }
348
349 =item delete_holiday
350
351         delete_holiday(weekday => $weekday
352                        day => $day,
353                        month => $month,
354                                    year => $year);
355
356 Delete a holiday for $self->{branchcode}.
357
358 C<$weekday> Is the week day to delete.
359
360 C<$day> Is the day month to make the date to delete.
361
362 C<$month> Is month to make the date to delete.
363
364 C<$year> Is year to make the date to delete.
365
366 =cut
367
368 sub delete_holiday {
369         my $self = shift @_;
370         my %options = @_;
371
372         # Verify what kind of holiday that day is. For example, if it is
373         # a repeatable holiday, this should check if there are some exception
374         # for that holiday rule. Otherwise, if it is a regular holiday, it´s 
375         # ok just deleting it.
376
377         my $dbh = C4::Context->dbh();
378         my $isSingleHoliday = $dbh->prepare("select id from special_holidays where (branchcode = '$self->{branchcode}') and (day = $options{day}) and (month = $options{month}) and (year = $options{year})");
379         $isSingleHoliday->execute;
380         if ($isSingleHoliday->rows) {
381                 my $id = $isSingleHoliday->fetchrow;
382                 $isSingleHoliday->finish; # Close the last query
383
384                 my $deleteHoliday = $dbh->prepare("delete from special_holidays where (id = $id)");
385                 $deleteHoliday->execute;
386                 $deleteHoliday->finish; # Close the last query
387                 delete($self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"});
388         } else {        
389                 $isSingleHoliday->finish; # Close the last query
390
391                 my $isWeekdayHoliday = $dbh->prepare("select id from repeatable_holidays where (branchcode = '$self->{branchcode}') and (weekday = $options{weekday})");
392                 $isWeekdayHoliday->execute;
393                 if ($isWeekdayHoliday->rows) {
394                         my $id = $isWeekdayHoliday->fetchrow;
395                         $isWeekdayHoliday->finish; # Close the last query
396
397                         my $updateExceptions = $dbh->prepare("update special_holidays set isexception = 0 where (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = $options{weekday}) and (branchcode = '$self->{branchcode}')");
398                         $updateExceptions->execute;
399                         $updateExceptions->finish; # Close the last query
400
401                         my $deleteHoliday = $dbh->prepare("delete from repeatable_holidays where (id = $id)");
402                         $deleteHoliday->execute;
403                         $deleteHoliday->finish;
404                         delete($self->{'week_days_holidays'}->{$options{weekday}});
405                 } else {
406                         $isWeekdayHoliday->finish; # Close the last query
407
408                         my $isDayMonthHoliday = $dbh->prepare("select id from repeatable_holidays where (branchcode = '$self->{branchcode}') (day = $options{day}) and (month = $options{month})");
409                         $isDayMonthHoliday->execute;
410                         if ($isDayMonthHoliday->rows) {
411                                 my $id = $isDayMonthHoliday->fetchrow;
412                                 $isDayMonthHoliday->finish;
413                                 my $updateExceptions = $dbh->prepare("update special_holidays set isexception = 0 where (special_holidays.branchcode = '$self->{branchcode}') and (special_holidays.day = $options{day}) and (special_holidays.month = $options{month})");
414                                 $updateExceptions->execute;
415                                 $updateExceptions->finish; # Close the last query
416
417                                 my $deleteHoliday = $dbh->prepare("delete from repeatable_holidays where (id = $id)");
418                                 $deleteHoliday->execute;
419                                 $deleteHoliday->finish; # Close the last query
420                                 $isDayMonthHoliday->finish; # Close the last query
421                                 delete($self->{'day_month_holidays'}->{"$options{month}/$options{day}"});
422                         }
423                 }
424         }       
425         return $self;
426 }
427
428 =item isHoliday
429         
430         $isHoliday = isHoliday($day, $month $year);
431
432
433 C<$day> Is the day to check wether if is a holiday or not.
434
435 C<$month> Is the month to check wether its a holiday or not.
436
437 C<$year> Is the year to check wether if its a holiday or not.
438
439 =cut
440
441 sub isHoliday {
442         my ($self, $day, $month, $year) = @_;
443
444         my $weekday = Date_DayOfWeek($month, $day, $year) % 7;  
445         my $weekDays = $self->get_week_days_holidays();
446         my $dayMonths = $self->get_day_month_holidays();
447         my $exceptions = $self->get_exception_holidays();
448         my $singles = $self->get_single_holidays();
449
450         if (defined($exceptions->{"$year/$month/$day"})) {
451                 return 0;
452         } else {                
453                 if ((exists($weekDays->{$weekday})) || 
454                         (exists($dayMonths->{"$month/$day"})) || 
455                         (exists($singles->{"$year/$month/$day"}))) {                    
456                         return 1;
457                 } else {
458                         return 0;
459                 }
460         }
461
462 }
463
464 =item addDate
465
466         my ($day, $month, $year) = $calendar->addDate($day, $month, $year, $offset)
467
468 C<$day> Is the starting day of the interval.
469
470 C<$month> Is the starting month of the interval.
471
472 C<$year> Is the starting year of the interval.
473
474 C<$offset> Is the number of days that this function has to count from $date.
475
476 =cut
477
478 sub addDate {
479         my ($self, $day, $month, $year, $offset) = @_;
480         if ($offset < 0) { # In case $offset is negative
481                 $offset = $offset*(-1);
482         }
483
484         my $daysMode = C4::Context->preference('useDaysMode');
485         if ($daysMode eq 'normal') {
486                 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, ($offset - 1));
487         } else {
488                 while ($offset > 0) {                                                           
489                         if (!($self->isHoliday($day, $month, $year))) {
490                                 $offset = $offset - 1;                                  
491                         }                               
492                         if ($offset > 0) {
493                                 ($year, $month, $day) = Add_Delta_Days($year, $month, $day, 1);
494                         }                               
495                 }
496         }
497         return($day, $month, $year);    
498 }
499
500 =item daysBetween
501
502         my $daysBetween = $calendar->daysBetween($dayFrom, $monthFrom, $yearFrom,
503                                                  $dayTo, $monthTo, $yearTo)
504
505 C<$dayFrom> Is the starting day of the interval.
506
507 C<$monthFrom> Is the starting month of the interval.
508
509 C<$yearFrom> Is the starting year of the interval.
510
511 C<$dayTo> Is the ending day of the interval.
512
513 C<$monthTo> Is the ending month of the interval.
514
515 C<$yearTo> Is the ending year of the interval.
516
517 =cut
518
519 sub daysBetween {
520         my ($self, $dayFrom, $monthFrom, $yearFrom, $dayTo, $monthTo, $yearTo) = @_;
521          
522         my $daysMode = C4::Context->preference('useDaysMode');
523         my $count = 1;
524         my $continue = 1;
525         if ($daysMode eq 'normal') {
526                 while ($continue) {
527                         if (($yearFrom != $yearTo) || ($monthFrom != $monthTo) || ($dayFrom != $dayTo)) {
528                                 ($yearFrom, $monthFrom, $dayFrom) = Add_Delta_Days($yearFrom, $monthFrom, $dayFrom, 1);
529                                 $count++;
530                         } else {
531                                 $continue = 0;  
532                         }
533                 }               
534         } else {
535                 while ($continue) {
536                         if (($yearFrom != $yearTo) || ($monthFrom != $monthTo) || ($dayFrom != $dayTo)) {
537                                 if (!($self->isHoliday($dayFrom, $monthFrom, $yearFrom))) {
538                                         $count++;
539                                 }       
540                                 ($yearFrom, $monthFrom, $dayFrom) = Add_Delta_Days($yearFrom, $monthFrom, $dayFrom, 1);                         
541                         } else {
542                                 $continue = 0;  
543                         }
544                 }               
545         }
546         return($count); 
547 }
548
549 sub Date_DayOfWeek{
550 my ($month, $day, $year)=@_;
551 my $date=$year."-".$month."-".$day;
552 my $dbh=C4::Context->dbh;
553 my $sth=$dbh->prepare("SELECT DAYOFWEEK(?)");
554 $sth->execute($date);
555 my $dayofweek=$sth->fetchrow;
556 return $dayofweek;
557 }
558
559 sub Add_Delta_Days{
560 my ($year, $month, $day, $offset)=@_;
561 my $date=$year."-".$month."-".$day;
562 my $dbh=C4::Context->dbh;
563 my $sth=$dbh->prepare(" SELECT DATE_ADD(?, INTERVAL ? DAY)");
564 $sth->execute($date,$offset);
565  $date=$sth->fetchrow;
566  ($year, $month, $day)=split /-/,$date;
567 return ($year, $month, $day);
568 }
569
570
571
572 1;
573
574 __END__
575
576 =back
577
578 =head1 AUTHOR
579
580 Koha Physics Library UNLP <matias_veleda@hotmail.com>
581 Modified by Tumer Garip NUE Grand Library --No more Date::Manip
582 =cut