Bug 5549 : Due Date calculation in issue needs to be HH:MM aware
[koha.git] / Koha / Calendar.pm
1 package Koha::Calendar;
2 use strict;
3 use warnings;
4 use 5.010;
5
6 use DateTime;
7 use C4::Context;
8 use Carp;
9 use Readonly;
10
11 sub new {
12     my ( $classname, %options ) = @_;
13     my $self = {};
14     for my $o_name ( keys %options ) {
15         my $o = lc $o_name;
16         $self->{$o} = $options{$o_name};
17     }
18     if ( !defined $self->{branchcode} ) {
19         croak 'No branchcode argument passed to Koha::Calendar->new';
20     }
21     bless $self, $classname;
22     $self->_init();
23     return $self;
24 }
25
26 sub _init {
27     my $self       = shift;
28     my $branch     = $self->{branchcode};
29     my $dbh        = C4::Context->dbh();
30     my $repeat_sth = $dbh->prepare(
31 'SELECT * from repeatable_holidays WHERE branchcode = ? AND ISNULL(weekday) = ?'
32     );
33     $repeat_sth->execute( $branch, 0 );
34     $self->{weekly_closed_days} = [];
35     Readonly::Scalar my $sunday => 7;
36     while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
37         my $day = $tuple->{weekday} == 0 ? $sunday : $tuple->{weekday};
38         push @{ $self->{weekly_closed_days} }, $day;
39     }
40     $repeat_sth->execute( $branch, 1 );
41     $self->{day_month_closed_days} = [];
42     while ( my $tuple = $repeat_sth->fetchrow_hashref ) {
43         push @{ $self->{day_month_closed_days} },
44           { day => $tuple->{day}, month => $tuple->{month}, };
45     }
46     my $special = $dbh->prepare(
47 'SELECT day, month, year, title, description FROM special_holidays WHERE ( branchcode = ? ) AND (isexception = ?)'
48     );
49     $special->execute( $branch, 1 );
50     my $dates = [];
51     while ( my ( $day, $month, $year, $title, $description ) =
52         $special->fetchrow ) {
53         push @{$dates},
54           DateTime->new(
55             day       => $day,
56             month     => $month,
57             year      => $year,
58             time_zone => C4::Context->tz()
59           )->truncate( to => 'day' );
60     }
61     $self->{exception_holidays} =
62       DateTime::Set->from_datetimes( dates => $dates );
63     $special->execute( $branch, 1 );
64     $dates = [];
65     while ( my ( $day, $month, $year, $title, $description ) =
66         $special->fetchrow ) {
67         push @{$dates},
68           DateTime->new(
69             day       => $day,
70             month     => $month,
71             year      => $year,
72             time_zone => C4::Context->tz()
73           )->truncate( to => 'day' );
74     }
75     $self->{single_holidays} = DateTime::Set->from_datetimes( dates => $dates );
76     return;
77 }
78
79 sub addDate {
80     my ( $self, $base_date, $add_duration, $unit ) = @_;
81     my $days_mode = C4::Context->preference('useDaysMode');
82     Readonly::Scalar my $return_by_hour => 10;
83     if ( $days_mode eq 'Datedue' ) {
84
85         my $dt = $base_date + $add_duration;
86         while ( $self->is_holiday($dt) ) {
87
88             # TODOP if hours set to 10 am
89             $dt->add( days => 1 );
90             if ( $unit eq 'hours' ) {
91                 $dt->set_hour($return_by_hour);    # Staffs specific
92             }
93         }
94         return $dt;
95     } elsif ( $days_mode eq 'Calendar' ) {
96         my $days = $add_duration->in_units('days');
97         while ($days) {
98             $base_date->add( days => 1 );
99             if ( $self->is_holiday($base_date) ) {
100                 next;
101             } else {
102                 --$days;
103             }
104         }
105         if ( $unit eq 'hours' ) {
106             my $dt = $base_date->clone()->subtract( days => 1 );
107             if ( $self->is_holiday($dt) ) {
108                 $base_date->set_hour($return_by_hour);    # Staffs specific
109             }
110         }
111         return $base_date;
112     } else {    # Days
113         return $base_date + $add_duration;
114     }
115 }
116
117 sub is_holiday {
118     my ( $self, $dt ) = @_;
119     my $dow = $dt->day_of_week;
120     my @matches = grep { $_ == $dow } @{ $self->{weekly_closed_days} };
121     if (@matches) {
122         return 1;
123     }
124     $dt->truncate('days');
125     my $day   = $dt->day;
126     my $month = $dt->month;
127     for my $dm ( @{ $self->{day_month_closed_days} } ) {
128         if ( $month == $dm->{month} && $day == $dm->{day} ) {
129             return 1;
130         }
131     }
132     if ( $self->{exception_holidays}->contains($dt) ) {
133         return 1;
134     }
135     if ( $self->{single_holidays}->contains($dt) ) {
136         return 1;
137     }
138
139     # damn have to go to work after all
140     return 0;
141 }
142
143 1;
144 __END__
145
146 =head1 NAME
147
148 Koha::Calendar - Object containing a branches calendar
149
150 =head1 VERSION
151
152 This documentation refers to Koha::Calendar version 0.0.1
153
154 =head1 SYNOPSIS
155
156   use Koha::Calendat
157
158   my $c = Koha::Calender->new( branchcode => 'MAIN' );
159   my $dt = DateTime->now();
160
161   # are we open
162   $open = $c->is_holiday($dt);
163   # when will item be due if loan period = $dur (a DateTime::Duration object)
164   $duedate = $c->addDate($dt,$dur,'days');
165
166
167 =head1 DESCRIPTION
168
169   Implements those features of C4::Calendar needed for Staffs Rolling Loans
170
171 =head1 METHODS
172
173 =head2 new : Create a calendar object
174
175 my $calendar = Koha::Calendar->new( branchcode => 'MAIN' );
176
177 The option branchcode is required
178
179
180 =head2 addDate
181
182     my $dt = $calendar->addDate($date, $dur, $unit)
183
184 C<$date> is a DateTime object representing the starting date of the interval.
185
186 C<$offset> is a DateTime::Duration to add to it
187
188 C<$unit> is a string value 'days' or 'hours' toflag granularity of duration
189
190 Currently unit is only used to invoke Staffs return Monday at 10 am rule this
191 parameter will be removed when issuingrules properly cope with that
192
193
194 =head2 is_holiday
195
196 $yesno = $calendar->is_holiday($dt);
197
198 passed at DateTime object returns 1 if it is a closed day
199 0 if not according to the calendar
200
201 =head1 DIAGNOSTICS
202
203 Will croak if not passed a branchcode in new
204
205 =head1 BUGS AND LIMITATIONS
206
207 This only contains a limited subset of the functionality in C4::Calendar
208 Only enough to support Staffs Rolling loans
209
210 =head1 AUTHOR
211
212 Colin Campbell colin.campbell@ptfs-europe.com
213
214 =head1 LICENSE AND COPYRIGHT
215
216 Copyright (c) 2011 PTFS-Europe Ltd All rights reserved
217
218 This program is free software: you can redistribute it and/or modify
219 it under the terms of the GNU General Public License as published by
220 the Free Software Foundation, either version 2 of the License, or
221 (at your option) any later version.
222
223 This program is distributed in the hope that it will be useful,
224 but WITHOUT ANY WARRANTY; without even the implied warranty of
225 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
226 GNU General Public License for more details.
227
228 You should have received a copy of the GNU General Public License
229 along with this program.  If not, see <http://www.gnu.org/licenses/>.