New styles for bulk hold and bulk tag inputs on search results page.
[koha.git] / C4 / Calendar.pm
1 package C4::Calendar;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it 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 use warnings;
20 use vars qw($VERSION @EXPORT);
21
22 use Carp;
23 use Date::Calc qw( Date_to_Days );
24
25 use C4::Context;
26
27 BEGIN {
28     # set the version for version checking
29     $VERSION = 3.01;
30     require Exporter;
31     @EXPORT = qw(
32         &get_week_days_holidays
33         &get_day_month_holidays
34         &get_exception_holidays 
35         &get_single_holidays
36         &insert_week_day_holiday
37         &insert_day_month_holiday
38         &insert_single_holiday
39         &insert_exception_holiday
40         &delete_holiday
41         &isHoliday
42         &addDate
43         &daysBetween
44     );
45 }
46
47 =head1 NAME
48
49 C4::Calendar::Calendar - Koha module dealing with holidays.
50
51 =head1 SYNOPSIS
52
53     use C4::Calendar::Calendar;
54
55 =head1 DESCRIPTION
56
57 This package is used to deal with holidays. Through this package, you can set all kind of holidays for the library.
58
59 =head1 FUNCTIONS
60
61 =over 2
62
63 =item new
64
65     $calendar = C4::Calendar->new(branchcode => $branchcode);
66
67 Each library branch has its own Calendar.  
68 C<$branchcode> specifies which Calendar you want.
69
70 =cut
71
72 sub new {
73     my $classname = shift @_;
74     my %options = @_;
75     my $self = bless({}, $classname);
76     foreach my $optionName (keys %options) {
77         $self->{lc($optionName)} = $options{$optionName};
78     }
79     defined($self->{branchcode}) or croak "No branchcode argument to new.  Should be C4::Calendar->new(branchcode => \$branchcode)";
80     $self->_init($self->{branchcode});
81     return $self;
82 }
83
84 sub _init {
85     my $self = shift @_;
86     my $branch = shift;
87     defined($branch) or die "No branchcode sent to _init";  # must test for defined here and above to allow ""
88     my $dbh = C4::Context->dbh();
89     my $repeatable = $dbh->prepare( 'SELECT *
90                                        FROM repeatable_holidays
91                                       WHERE ( branchcode = ? )
92                                         AND (ISNULL(weekday) = ?)' );
93     $repeatable->execute($branch,0);
94     my %week_days_holidays;
95     while (my $row = $repeatable->fetchrow_hashref) {
96         my $key = $row->{weekday};
97         $week_days_holidays{$key}{title}       = $row->{title};
98         $week_days_holidays{$key}{description} = $row->{description};
99     }
100     $self->{'week_days_holidays'} = \%week_days_holidays;
101
102     $repeatable->execute($branch,1);
103     my %day_month_holidays;
104     while (my $row = $repeatable->fetchrow_hashref) {
105         my $key = $row->{month} . "/" . $row->{day};
106         $day_month_holidays{$key}{title}       = $row->{title};
107         $day_month_holidays{$key}{description} = $row->{description}
108     }
109     $self->{'day_month_holidays'} = \%day_month_holidays;
110
111     my $special = $dbh->prepare( 'SELECT day, month, year, title, description
112                                     FROM special_holidays
113                                    WHERE ( branchcode = ? )
114                                      AND (isexception = ?)' );
115     $special->execute($branch,1);
116     my %exception_holidays;
117     while (my ($day, $month, $year, $title, $description) = $special->fetchrow) {
118         $exception_holidays{"$year/$month/$day"}{title} = $title;
119         $exception_holidays{"$year/$month/$day"}{description} = $description;
120     }
121     $self->{'exception_holidays'} = \%exception_holidays;
122
123     $special->execute($branch,0);
124     my %single_holidays;
125     while (my ($day, $month, $year, $title, $description) = $special->fetchrow) {
126         $single_holidays{"$year/$month/$day"}{title} = $title;
127         $single_holidays{"$year/$month/$day"}{description} = $description;
128     }
129     $self->{'single_holidays'} = \%single_holidays;
130     return $self;
131 }
132
133 =item get_week_days_holidays
134
135     $week_days_holidays = $calendar->get_week_days_holidays();
136
137 Returns a hash reference to week days holidays.
138
139 =cut
140
141 sub get_week_days_holidays {
142     my $self = shift @_;
143     my $week_days_holidays = $self->{'week_days_holidays'};
144     return $week_days_holidays;
145 }
146
147 =item get_day_month_holidays
148     
149     $day_month_holidays = $calendar->get_day_month_holidays();
150
151 Returns a hash reference to day month holidays.
152
153 =cut
154
155 sub get_day_month_holidays {
156     my $self = shift @_;
157     my $day_month_holidays = $self->{'day_month_holidays'};
158     return $day_month_holidays;
159 }
160
161 =item get_exception_holidays
162     
163     $exception_holidays = $calendar->exception_holidays();
164
165 Returns a hash reference to exception holidays. This kind of days are those
166 which stands for a holiday, but you wanted to make an exception for this particular
167 date.
168
169 =cut
170
171 sub get_exception_holidays {
172     my $self = shift @_;
173     my $exception_holidays = $self->{'exception_holidays'};
174     return $exception_holidays;
175 }
176
177 =item get_single_holidays
178     
179     $single_holidays = $calendar->get_single_holidays();
180
181 Returns a hash reference to single holidays. This kind of holidays are those which
182 happend just one time.
183
184 =cut
185
186 sub get_single_holidays {
187     my $self = shift @_;
188     my $single_holidays = $self->{'single_holidays'};
189     return $single_holidays;
190 }
191
192 =item insert_week_day_holiday
193
194     insert_week_day_holiday(weekday => $weekday,
195                             title => $title,
196                             description => $description);
197
198 Inserts a new week day for $self->{branchcode}.
199
200 C<$day> Is the week day to make holiday.
201
202 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
203
204 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
205
206 =cut
207
208 sub insert_week_day_holiday {
209     my $self = shift @_;
210     my %options = @_;
211
212     my $dbh = C4::Context->dbh();
213     my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (id,branchcode,weekday,day,month,title,description) values ( '',?,?,NULL,NULL,?,? )"); 
214         $insertHoliday->execute( $self->{branchcode}, $options{weekday},$options{title}, $options{description});
215     $self->{'week_days_holidays'}->{$options{weekday}}{title} = $options{title};
216     $self->{'week_days_holidays'}->{$options{weekday}}{description} = $options{description};
217     return $self;
218 }
219
220 =item insert_day_month_holiday
221
222     insert_day_month_holiday(day => $day,
223                              month => $month,
224                              title => $title,
225                              description => $description);
226
227 Inserts a new day month holiday for $self->{branchcode}.
228
229 C<$day> Is the day month to make the date to insert.
230
231 C<$month> Is month to make the date to insert.
232
233 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
234
235 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
236
237 =cut
238
239 sub insert_day_month_holiday {
240     my $self = shift @_;
241     my %options = @_;
242
243     my $dbh = C4::Context->dbh();
244     my $insertHoliday = $dbh->prepare("insert into repeatable_holidays (id,branchcode,weekday,day,month,title,description) values ('', ?, NULL, ?, ?, ?,? )");
245         $insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{title}, $options{description});
246     $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{title} = $options{title};
247     $self->{'day_month_holidays'}->{"$options{month}/$options{day}"}{description} = $options{description};
248     return $self;
249 }
250
251 =item insert_single_holiday
252
253     insert_single_holiday(day => $day,
254                           month => $month,
255                           year => $year,
256                           title => $title,
257                           description => $description);
258
259 Inserts a new single holiday for $self->{branchcode}.
260
261 C<$day> Is the day month to make the date to insert.
262
263 C<$month> Is month to make the date to insert.
264
265 C<$year> Is year to make the date to insert.
266
267 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
268
269 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
270
271 =cut
272
273 sub insert_single_holiday {
274     my $self = shift @_;
275     my %options = @_;
276     
277         my $dbh = C4::Context->dbh();
278     my $isexception = 0;
279     my $insertHoliday = $dbh->prepare("insert into special_holidays (id,branchcode,day,month,year,isexception,title,description) values ('', ?,?,?,?,?,?,?)");
280         $insertHoliday->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description});
281     $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
282     $self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
283     return $self;
284 }
285
286 =item insert_exception_holiday
287
288     insert_exception_holiday(day => $day,
289                              month => $month,
290                              year => $year,
291                              title => $title,
292                              description => $description);
293
294 Inserts a new exception holiday for $self->{branchcode}.
295
296 C<$day> Is the day month to make the date to insert.
297
298 C<$month> Is month to make the date to insert.
299
300 C<$year> Is year to make the date to insert.
301
302 C<$title> Is the title to store for the holiday formed by $year/$month/$day.
303
304 C<$description> Is the description to store for the holiday formed by $year/$month/$day.
305
306 =cut
307
308 sub insert_exception_holiday {
309     my $self = shift @_;
310     my %options = @_;
311
312     my $dbh = C4::Context->dbh();
313     my $isexception = 1;
314     my $insertException = $dbh->prepare("insert into special_holidays (id,branchcode,day,month,year,isexception,title,description) values ('', ?,?,?,?,?,?,?)");
315         $insertException->execute( $self->{branchcode}, $options{day},$options{month},$options{year}, $isexception, $options{title}, $options{description});
316     $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{title} = $options{title};
317     $self->{'exception_holidays'}->{"$options{year}/$options{month}/$options{day}"}{description} = $options{description};
318     return $self;
319 }
320
321 =item delete_holiday
322
323     delete_holiday(weekday => $weekday
324                    day => $day,
325                    month => $month,
326                    year => $year);
327
328 Delete a holiday for $self->{branchcode}.
329
330 C<$weekday> Is the week day to delete.
331
332 C<$day> Is the day month to make the date to delete.
333
334 C<$month> Is month to make the date to delete.
335
336 C<$year> Is year to make the date to delete.
337
338 =cut
339
340 sub delete_holiday {
341     my $self = shift @_;
342     my %options = @_;
343
344     # Verify what kind of holiday that day is. For example, if it is
345     # a repeatable holiday, this should check if there are some exception
346         # for that holiday rule. Otherwise, if it is a regular holiday, it´s 
347     # ok just deleting it.
348
349     my $dbh = C4::Context->dbh();
350     my $isSingleHoliday = $dbh->prepare("SELECT id FROM special_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?) AND (year = ?)");
351     $isSingleHoliday->execute($self->{branchcode}, $options{day}, $options{month}, $options{year});
352     if ($isSingleHoliday->rows) {
353         my $id = $isSingleHoliday->fetchrow;
354         $isSingleHoliday->finish; # Close the last query
355
356         my $deleteHoliday = $dbh->prepare("DELETE FROM special_holidays WHERE id = ?");
357         $deleteHoliday->execute($id);
358         delete($self->{'single_holidays'}->{"$options{year}/$options{month}/$options{day}"});
359     } else {
360         $isSingleHoliday->finish; # Close the last query
361
362         my $isWeekdayHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE branchcode = ? AND weekday = ?");
363         $isWeekdayHoliday->execute($self->{branchcode}, $options{weekday});
364         if ($isWeekdayHoliday->rows) {
365             my $id = $isWeekdayHoliday->fetchrow;
366             $isWeekdayHoliday->finish; # Close the last query
367
368             my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (WEEKDAY(CONCAT(special_holidays.year,'-',special_holidays.month,'-',special_holidays.day)) = ?) AND (branchcode = ?)");
369             $updateExceptions->execute($options{weekday}, $self->{branchcode});
370             $updateExceptions->finish; # Close the last query
371
372             my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE id = ?");
373             $deleteHoliday->execute($id);
374             delete($self->{'week_days_holidays'}->{$options{weekday}});
375         } else {
376             $isWeekdayHoliday->finish; # Close the last query
377
378             my $isDayMonthHoliday = $dbh->prepare("SELECT id FROM repeatable_holidays WHERE (branchcode = ?) AND (day = ?) AND (month = ?)");
379             $isDayMonthHoliday->execute($self->{branchcode}, $options{day}, $options{month});
380             if ($isDayMonthHoliday->rows) {
381                 my $id = $isDayMonthHoliday->fetchrow;
382                 $isDayMonthHoliday->finish;
383                 my $updateExceptions = $dbh->prepare("UPDATE special_holidays SET isexception = 0 WHERE (special_holidays.branchcode = ?) AND (special_holidays.day = ?) and (special_holidays.month = ?)");
384                 $updateExceptions->execute($self->{branchcode}, $options{day}, $options{month});
385                 $updateExceptions->finish; # Close the last query
386
387                 my $deleteHoliday = $dbh->prepare("DELETE FROM repeatable_holidays WHERE (id = ?)");
388                 $deleteHoliday->execute($id);
389                 delete($self->{'day_month_holidays'}->{"$options{month}/$options{day}"});
390             }
391         }
392     }
393     return $self;
394 }
395
396 =item isHoliday
397     
398     $isHoliday = isHoliday($day, $month $year);
399
400
401 C<$day> Is the day to check whether if is a holiday or not.
402
403 C<$month> Is the month to check whether if is a holiday or not.
404
405 C<$year> Is the year to check whether if is a holiday or not.
406
407 =cut
408
409 sub isHoliday {
410     my ($self, $day, $month, $year) = @_;
411         # FIXME - date strings are stored in non-padded metric format. should change to iso.
412         # FIXME - should change arguments to accept C4::Dates object
413         $month=$month+0;
414         $year=$year+0;
415         $day=$day+0;
416     my $weekday = &Date::Calc::Day_of_Week($year, $month, $day) % 7; 
417     my $weekDays   = $self->get_week_days_holidays();
418     my $dayMonths  = $self->get_day_month_holidays();
419     my $exceptions = $self->get_exception_holidays();
420     my $singles    = $self->get_single_holidays();
421     if (defined($exceptions->{"$year/$month/$day"})) {
422         return 0;
423     } else {
424         if ((exists($weekDays->{$weekday})) ||
425             (exists($dayMonths->{"$month/$day"})) ||
426             (exists($singles->{"$year/$month/$day"}))) {
427                         return 1;
428         } else {
429             return 0;
430         }
431     }
432
433 }
434
435 =item addDate
436
437     my ($day, $month, $year) = $calendar->addDate($date, $offset)
438
439 C<$date> is a C4::Dates object representing the starting date of the interval.
440
441 C<$offset> Is the number of days that this function has to count from $date.
442
443 =cut
444
445 sub addDate {
446     my ($self, $startdate, $offset) = @_;
447     my ($year,$month,$day) = split("-",$startdate->output('iso'));
448         my $daystep = 1;
449         if ($offset < 0) { # In case $offset is negative
450        # $offset = $offset*(-1);
451                 $daystep = -1;
452     }
453         my $daysMode = C4::Context->preference('useDaysMode');
454     if ($daysMode eq 'Datedue') {
455         ($year, $month, $day) = &Date::Calc::Add_Delta_Days($year, $month, $day, $offset );
456                 while ($self->isHoliday($day, $month, $year)) {
457             ($year, $month, $day) = &Date::Calc::Add_Delta_Days($year, $month, $day, $daystep);
458         }
459     } elsif($daysMode eq 'Calendar') {
460         while ($offset !=  0) {
461             ($year, $month, $day) = &Date::Calc::Add_Delta_Days($year, $month, $day, $daystep);
462             if (!($self->isHoliday($day, $month, $year))) {
463                 $offset = $offset - $daystep;
464                         }
465         }
466         } else { ## ($daysMode eq 'Days') 
467         ($year, $month, $day) = &Date::Calc::Add_Delta_Days($year, $month, $day, $offset );
468     }
469     return(C4::Dates->new( sprintf("%04d-%02d-%02d",$year,$month,$day),'iso'));
470 }
471
472 =item daysBetween
473
474     my $daysBetween = $calendar->daysBetween($startdate, $enddate)
475
476 C<$startdate> and C<$enddate> are C4::Dates objects that define the interval.
477
478 Returns the number of non-holiday days in the interval.
479 useDaysMode syspref has no effect here.
480 =cut
481
482 sub daysBetween ($$$) {
483     my $self      = shift or return undef;
484     my $startdate = shift or return undef;
485     my $enddate   = shift or return undef;
486         my ($yearFrom,$monthFrom,$dayFrom) = split("-",$startdate->output('iso'));
487         my ($yearTo,  $monthTo,  $dayTo  ) = split("-",  $enddate->output('iso'));
488         if (Date_to_Days($yearFrom,$monthFrom,$dayFrom) > Date_to_Days($yearTo,$monthTo,$dayTo)) {
489                 return 0;
490                 # we don't go backwards  ( FIXME - handle this error better )
491         }
492     my $count = 0;
493     while (1) {
494         ($yearFrom != $yearTo or $monthFrom != $monthTo or $dayFrom != $dayTo) or last; # if they all match, it's the last day
495         unless ($self->isHoliday($dayFrom, $monthFrom, $yearFrom)) {
496             $count++;
497         }
498         ($yearFrom, $monthFrom, $dayFrom) = &Date::Calc::Add_Delta_Days($yearFrom, $monthFrom, $dayFrom, 1);
499     }
500     return($count);
501 }
502
503 1;
504
505 __END__
506
507 =back
508
509 =head1 AUTHOR
510
511 Koha Physics Library UNLP <matias_veleda@hotmail.com>
512
513 =cut