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