Bug 30650: Add filter_by_scheduled_today
[koha.git] / Koha / CurbsidePickup.pm
1 package Koha::CurbsidePickup;
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 use Modern::Perl;
19
20 use Carp;
21
22 use Koha::Database;
23
24 use base qw(Koha::Object);
25
26 use C4::Circulation qw( CanBookBeIssued AddIssue );
27 use C4::Members::Messaging qw( GetMessagingPreferences );
28 use C4::Letters qw( GetPreparedLetter EnqueueLetter );
29 use Koha::DateUtils qw( dt_from_string );
30 use Koha::Patron;
31 use Koha::Library;
32 use Koha::CurbsidePickupIssues;
33 use Koha::Exceptions::CurbsidePickup;
34
35 =head1 NAME
36
37 Koha::CurbsidePickup - Koha Curbside Pickup Object class
38
39 =head1 API
40
41 =head2 Class methods
42
43 =cut
44
45 =head3 new
46
47 =cut
48
49 sub new {
50     my ( $self, $params ) = @_;
51
52     my $policy =
53       Koha::CurbsidePickupPolicies->find( { branchcode => $params->{branchcode} } );
54
55     Koha::Exceptions::CurbsidePickup::NotEnabled->throw
56       unless $policy && $policy->enabled;
57
58     if ( $policy->enable_waiting_holds_only ) {
59         my $patron        = Koha::Patrons->find( $params->{borrowernumber} );
60         my $waiting_holds = $patron->holds->search(
61             { found => 'W', branchcode => $params->{branchcode} } );
62
63         Koha::Exceptions::CurbsidePickup::NoWaitingHolds->throw
64           unless $waiting_holds->count;
65     }
66     my $existing_curbside_pickups = Koha::CurbsidePickups->search(
67         {
68             branchcode                => $params->{branchcode},
69             borrowernumber            => $params->{borrowernumber},
70             delivered_datetime        => undef,
71         }
72     )->filter_by_scheduled_today;
73     Koha::Exceptions::CurbsidePickup::TooManyPickups->throw(
74         branchcode     => $params->{branchcode},
75         borrowernumber => $params->{borrowernumber}
76     ) if $existing_curbside_pickups->count;
77
78     my $is_valid =
79       $policy->is_valid_pickup_datetime( $params->{scheduled_pickup_datetime} );
80     unless ($is_valid) {
81         my $error = @{ $is_valid->messages }[0]->message;
82         Koha::Exceptions::CurbsidePickup::NoMatchingSlots->throw
83           if $error eq 'no_matching_slots';
84         Koha::Exceptions::CurbsidePickup::NoMorePickupsAvailable->throw
85           if $error eq 'no_more_available';
86         Koha::Exceptions->throw(
87             "Error message must raise the appropriate exception");
88     }
89
90     return $self->SUPER::new($params);
91 }
92
93 =head3 notify_new_pickup
94
95 $pickup->notify_new_pickup
96
97 Will notify the patron that the pickup has been created.
98 Letter 'NEW_CURBSIDE_PICKUP will be used', and depending on 'Hold_Filled' configuration.
99
100 =cut
101
102 sub notify_new_pickup {
103     my ( $self ) = @_;
104
105     my $patron = $self->patron;
106
107     my $library = $self->library;
108
109     $patron->queue_notice({ letter_params => {
110         module     => 'reserves',
111         letter_code => 'NEW_CURBSIDE_PICKUP',
112         borrowernumber => $patron->borrowernumber,
113         branchcode => $self->branchcode,
114         tables     => {
115             'branches'  => $library->unblessed,
116             'borrowers' => $patron->unblessed,
117         },
118         substitute => {
119             curbside_pickup => $self,
120         }
121     }, message_name => 'Hold_Filled' });
122 }
123
124 =head3 checkouts
125
126 Return the checkouts linked to this pickup
127
128 =cut
129
130 sub checkouts {
131     my ( $self ) = @_;
132
133     my @pi = Koha::CurbsidePickupIssues->search({ curbside_pickup_id => $self->id })->as_list;
134
135     my @checkouts = map { $_->checkout } @pi;
136     @checkouts = grep { defined $_ } @checkouts;
137
138     return @checkouts;
139 }
140
141 =head3 patron
142
143 Return the patron linked to this pickup
144
145 =cut
146
147 sub patron {
148     my ( $self ) = @_;
149     my $rs = $self->_result->borrowernumber;
150     return unless $rs;
151     return Koha::Patron->_new_from_dbic( $rs );
152 }
153
154 =head3 staged_by_staff
155
156 Return the staff member that staged this pickup
157
158 =cut
159
160 sub staged_by_staff {
161     my ( $self ) = @_;
162     my $rs = $self->_result->staged_by;
163     return unless $rs;
164     return Koha::Patron->_new_from_dbic( $rs );
165 }
166
167 =head3 library
168
169 Return the branch associated with this pickup
170
171 =cut
172
173 sub library {
174     my ( $self ) = @_;
175     my $rs = $self->_result->branchcode;
176     return unless $rs;
177     return Koha::Library->_new_from_dbic( $rs );
178 }
179
180 =head3 mark_as_staged
181
182 Mark the pickup as staged
183
184 =cut
185
186 sub mark_as_staged {
187     my ( $self ) = @_;
188     my $staged_by = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
189     $self->set(
190         {
191             staged_datetime  => dt_from_string(),
192             staged_by        => $staged_by,
193             arrival_datetime => undef,
194         }
195     )->store;
196 }
197
198 =head3 mark_as_unstaged
199
200 Mark the pickup as unstaged
201
202 =cut
203
204 sub mark_as_unstaged {
205     my ( $self ) = @_;
206
207     $self->set(
208         {
209             staged_datetime  => undef,
210             staged_by        => undef,
211             arrival_datetime => undef,
212         }
213     )->store;
214 }
215
216 =head3 mark_patron_has_arrived
217
218 Set the arrival time of the patron
219
220 =cut
221
222 sub mark_patron_has_arrived {
223     my ( $self ) = @_;
224     $self->set(
225         {
226             arrival_datetime => dt_from_string(),
227         }
228     )->store;
229 }
230
231 =head3 mark_as_delivered
232
233 Mark the pickup as delivered. The waiting holds will be filled.
234
235 =cut
236
237 sub mark_as_delivered {
238     my ( $self ) = @_;
239     my $patron          = $self->patron;
240     my $holds           = $patron->holds;
241     my $branchcode = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
242     foreach my $hold ( $holds->as_list ) {
243         if ( $hold->found eq 'W' && $branchcode && $hold->branchcode eq $branchcode ) {
244             my ( $issuingimpossible, $needsconfirmation ) =
245               C4::Circulation::CanBookBeIssued( $patron, $hold->item->barcode );
246
247             unless ( keys %$issuingimpossible ) {
248                 my $issue =
249                   C4::Circulation::AddIssue( $patron->unblessed, $hold->item->barcode );
250                 if ($issue) {
251                     Koha::CurbsidePickupIssue->new(
252                         {
253                             curbside_pickup_id => $self->id,
254                             issue_id           => $issue->id,
255                             reserve_id         => $hold->id,
256                         }
257                     )->store();
258                 }
259                 else {
260                     Koha::Exceptions->throw(sprintf("Cannot checkout hold %s for patron %s: %s", $patron->id, $hold->id, join(", ", keys %$issuingimpossible)));
261                 }
262             }
263         }
264     }
265
266     my $delivered_by = C4::Context->userenv ? C4::Context->userenv->{number} : undef;
267     $self->arrival_datetime(dt_from_string) unless $self->arrival_datetime;
268     $self->set(
269         {
270             delivered_datetime => dt_from_string(),
271             delivered_by       => $delivered_by,
272         }
273     )->store;
274 }
275
276 =head3 status
277
278 Return the status of the pickup, can be 'to-be-staged', 'staged-and-ready', 'patron-is-outside' or 'delivered'.
279
280 =cut
281
282 sub status {
283     my ($self) = @_;
284     return
285         !defined $self->staged_datetime    ? 'to-be-staged'
286       : !defined $self->arrival_datetime   ? 'staged-and-ready'
287       : !defined $self->delivered_datetime ? 'patron-is-outside'
288       :                                      'delivered';
289 }
290
291 =head2 Internal methods
292
293 =head3 _type
294
295 =cut
296
297 sub _type {
298     return 'CurbsidePickup';
299 }
300
301 1;