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