LP#1612752 - Add cancel_time to action.transit_copy and friends.
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Circ / Transit.pm
1 package OpenILS::Application::Circ::Transit;
2 use base 'OpenILS::Application';
3 use strict; use warnings;
4 use OpenSRF::EX qw(:try);
5 use Data::Dumper;
6 use OpenSRF::Utils;
7 use OpenSRF::Utils::Cache;
8 use Digest::MD5 qw(md5_hex);
9 use OpenILS::Utils::CStoreEditor qw/:funcs/;
10 use OpenILS::Application::AppUtils;
11 use OpenILS::Application::Circ::Holds;
12 use OpenSRF::Utils::Logger qw(:logger);
13 use OpenSRF::AppSession;
14 use OpenILS::Const qw/:const/;
15
16 my $U                           = "OpenILS::Application::AppUtils";
17 my $holdcode                = "OpenILS::Application::Circ::Holds";
18 $Data::Dumper::Indent   = 0;
19
20
21
22 __PACKAGE__->register_method(
23     method  => "copy_transit_receive",
24     api_name    => "open-ils.circ.copy_transit.receive",
25     notes       => q/
26         Closes out a copy transit
27         Requestor needs the COPY_TRANSIT_RECEIVE permission
28         @param authtoken The login session key
29         @param params An object of named params including
30             copyid - the id of the copy in quest
31             barcode - the barcode of the copy in question 
32                 If copyid is not sent, this is used.
33         @return A ROUTE_ITEM if the copy is destined for a different location.
34             A SUCCESS event on success. Other events on error.
35     /);
36
37 sub copy_transit_receive {
38     my( $self, $client, $authtoken, $params ) = @_;
39     my %params = %$params;
40     my( $evt, $copy, $requestor );
41     ($requestor, $evt) = $U->checksesperm($authtoken, 'COPY_TRANSIT_RECEIVE');
42     return $evt if $evt;
43     ($copy, $evt) = $U->fetch_copy($params{copyid});
44     ($copy, $evt) = $U->fetch_copy_by_barcode($params{barcode}) unless $copy;
45     return $evt if $evt;
46     my $session = $U->start_db_session();
47     $U->set_audit_info($session, $authtoken, $requestor->id, $requestor->wsid);
48     $evt = transit_receive( $self, $copy, $requestor, $session );
49     $U->commit_db_session($session) if $U->event_equals($evt,'SUCCESS');
50     return $evt;
51 }
52
53 # ------------------------------------------------------------------------------
54 # If the transit destination is different than the requestor's lib,
55 # a ROUTE_TO event is returned with the org set.
56 # If 
57 # ------------------------------------------------------------------------------
58 sub transit_receive {
59     my ( $class, $copy, $requestor, $session ) = @_;
60     $U->logmark;
61
62     my( $transit, $evt );
63     my $copyid = $copy->id;
64
65     my $status_name = $U->copy_status_to_name($copy->status);
66     $logger->debug("Attempting transit receive on copy $copyid. Copy status is $status_name");
67
68     # fetch the transit
69     ($transit, $evt) = $U->fetch_open_transit_by_copy($copyid);
70     return $evt if $evt;
71
72     if( $transit->dest != $requestor->ws_ou ) {
73         $logger->activity("Fowarding transit on copy which is destined ".
74             "for a different location. copy=$copyid,current ".
75             "location=".$requestor->ws_ou.",destination location=".$transit->dest);
76
77         return OpenILS::Event->new('ROUTE_ITEM', org => $transit->dest );
78     }
79
80     # The transit is received, set the receive time
81     $transit->dest_recv_time('now');
82     my $r = $session->request(
83         'open-ils.storage.direct.action.transit_copy.update', $transit )->gather(1);
84     return $U->DB_UPDATE_FAILED($transit) unless $r;
85
86     my $ishold  = 0;
87     my ($ht)        = $U->fetch_hold_transit( $transit->id );
88     if($ht) {
89         $logger->info("Hold transit found in transit receive...");
90         $ishold = 1;
91     }
92
93     $logger->info("Recovering original copy status in transit: ".$transit->copy_status);
94     $copy->status( $transit->copy_status );
95     return $evt if ( $evt = 
96         $U->update_copy( copy => $copy, editor => $requestor->id, session => $session ));
97
98     return OpenILS::Event->new('SUCCESS', ishold => $ishold, 
99         payload => { transit => $transit, holdtransit => $ht } );
100 }
101
102
103
104
105
106 __PACKAGE__->register_method(
107     method  => "copy_transit_create",
108     api_name    => "open-ils.circ.copy_transit.create",
109     notes       => q/
110         Creates a new copy transit.  Requestor must have the 
111         CREATE_COPY_TRANSIT permission.
112         @param authtoken The login session key
113         @param params A param object containing the following keys:
114             copyid      - the copy id
115             destination - the id of the org destination.  If not defined,
116                 defaults to the copy's circ_lib
117         @return SUCCESS event on success, other event on error
118     /);
119
120 sub copy_transit_create {
121
122     my( $self, $client, $authtoken, $params ) = @_;
123     my %params = %$params;
124
125     my( $requestor, $evt ) = 
126         $U->checksesperm( $authtoken, 'CREATE_COPY_TRANSIT' );
127     return $evt if $evt;
128
129     my $copy;
130     ($copy,$evt) = $U->fetch_copy($params{copyid});
131     return $evt if $evt;
132
133     my $session     = $params{session} || $U->start_db_session();
134     my $source      = $requestor->home_ou;
135     my $dest            = $params{destination} || $copy->circ_lib;
136     my $transit     = Fieldmapper::action::transit_copy->new;
137     $U->set_audit_info($session, $authtoken, $requestor->id, $requestor->wsid);
138
139     $logger->activity("User ". $requestor->id ." creating a ".
140         " new copy transit for copy ".$copy->id." to org $dest");
141
142     $transit->source($source);
143     $transit->dest($dest);
144     $transit->target_copy($copy->id);
145     $transit->source_send_time("now");
146     $transit->copy_status($copy->status);
147     
148     $logger->debug("Creating new copy_transit in DB");
149
150     my $s = $session->request(
151         "open-ils.storage.direct.action.transit_copy.create", $transit )->gather(1);
152     return $U->DB_UPDATE_FAILED($transit) unless $s;
153     
154     my $stat = $U->copy_status_from_name('in transit');
155
156     $copy->status($stat->id); 
157     return $evt if ($evt = $U->update_copy(
158         copy => $copy, editor => $requestor->id, session => $session ));
159
160     $U->commit_db_session($session) unless $params{session};
161
162     return OpenILS::Event->new('SUCCESS', 
163         payload => { copy => $copy, transit => $transit } );
164 }
165
166
167 __PACKAGE__->register_method(
168     method => 'abort_transit',
169     api_name    => 'open-ils.circ.transit.abort',
170     signature   => q/
171         Deletes a cleans up a transit
172     /
173 );
174
175 sub abort_transit {
176     my( $self, $conn, $authtoken, $params ) = @_;
177
178     my $copyid      = $$params{copyid};
179     my $barcode     = $$params{barcode};
180     my $transitid   = $$params{transitid};
181
182     my $copy;
183     my $transit;
184     my $evt;
185
186     my $e = new_editor(xact => 1, authtoken => $authtoken);
187     return $e->event unless $e->checkauth;
188     return $e->event unless $e->allowed('ABORT_TRANSIT');
189
190     # ---------------------------------------------------------------------
191     # Find the related copy and/or transit based on whatever data we have
192     if( $barcode ) {
193         $copy = $e->search_asset_copy({barcode=>$barcode, deleted => 'f'})->[0];
194         return $e->event unless $copy;
195
196     } elsif( $copyid ) {
197         $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
198     }
199
200     if( $transitid ) {
201         $transit = $e->retrieve_action_transit_copy($transitid)
202             or return $e->event;
203
204     } elsif( $copy ) {
205
206         $transit = $e->search_action_transit_copy(
207             { target_copy => $copy->id, dest_recv_time => undef, cancel_time => undef })->[0];
208         return $e->event unless $transit;
209     }
210
211     if($transit and !$copy) {
212         $copy = $e->retrieve_asset_copy($transit->target_copy)
213             or return $e->event;
214     }
215     # ---------------------------------------------------------------------
216
217     return __abort_transit( $e, $transit, $copy );
218 }
219
220
221
222 sub __abort_transit {
223
224     my( $e, $transit, $copy, $no_reset_hold, $no_commit ) = @_;
225
226     my $evt;
227     my $hold;
228
229     if( (($transit->copy_status == OILS_COPY_STATUS_LOST || $transit->copy_status == OILS_COPY_STATUS_LOST_AND_PAID)
230              and !$e->allowed('ABORT_TRANSIT_ON_LOST')) or
231         ($transit->copy_status == OILS_COPY_STATUS_MISSING and !$e->allowed('ABORT_TRANSIT_ON_MISSING')) ) {
232         $e->rollback;
233         return OpenILS::Event->new('TRANSIT_ABORT_NOT_ALLOWED', copy_status => $transit->copy_status);
234     }
235
236
237     if( $transit->dest != $e->requestor->ws_ou 
238         and $transit->source != $e->requestor->ws_ou ) {
239         return $e->die_event unless $e->allowed('ABORT_REMOTE_TRANSIT', $e->requestor->ws_ou);
240     }
241
242     my $holdtransit = $e->retrieve_action_hold_transit_copy($transit->id);
243
244     # rather than deleting the transit row, set the cancel_time
245     $transit->cancel_time('now');
246     return $e->die_event unless $e->update_action_transit_copy($transit);
247
248     # Only change the copy status if the copy status is "In Transit."
249     if ($copy->status == OILS_COPY_STATUS_IN_TRANSIT) {
250         # if the status would normally result in 'Reshelving' once the item is checked in
251         if ($transit->copy_status == OILS_COPY_STATUS_AVAILABLE   ||
252             $transit->copy_status == OILS_COPY_STATUS_CHECKED_OUT ||
253             $transit->copy_status == OILS_COPY_STATUS_IN_PROCESS  ||
254             $transit->copy_status == OILS_COPY_STATUS_ON_HOLDS_SHELF  ||
255             $transit->copy_status == OILS_COPY_STATUS_IN_TRANSIT  ||
256             $transit->copy_status == OILS_COPY_STATUS_CATALOGING  ||
257             $transit->copy_status == OILS_COPY_STATUS_ON_RESV_SHELF  ||
258             $transit->copy_status == OILS_COPY_STATUS_RESHELVING) {
259             # set copy to Canceled Transit
260             $copy->status( OILS_COPY_STATUS_CANCELED_TRANSIT);
261         } else {
262             # recover the copy status
263             $copy->status($transit->copy_status);
264         }
265         $copy->editor( $e->requestor->id );
266         $copy->edit_date('now');
267
268         return $e->die_event unless $e->update_asset_copy($copy);
269     }
270
271     $e->commit unless $no_commit;
272
273     # if this is a hold transit, un-capture/un-target the hold
274     if($holdtransit and !$no_reset_hold) {
275         $hold = $e->retrieve_action_hold_request($holdtransit->hold) 
276             or return $e->die_event;
277         $evt = $holdcode->_reset_hold( $e->requestor, $hold );
278         return $evt if $evt;
279     }
280
281     return 1;
282 }
283
284
285 __PACKAGE__->register_method(
286     method      => 'get_open_copy_transit',
287     api_name        => 'open-ils.circ.open_copy_transit.retrieve',
288     signature   => q/
289         Retrieves the open transit object for a given copy
290         @param auth The login session key
291         @param copyid The id of the copy
292         @return Transit object
293  /
294 );
295
296 sub get_open_copy_transit {
297     my( $self, $conn, $auth, $copyid ) = @_;    
298     my $e = new_editor(authtoken=>$auth);
299     return $e->event unless $e->checkauth;
300     return $e->event unless $e->allowed('VIEW_USER'); # XXX rely on editor perms
301     my $t = $e->search_action_transit_copy(
302         { target_copy => $copyid, dest_recv_time => undef, cancel_time => undef });
303     return $e->event unless @$t;
304     return $$t[0];
305 }
306
307
308
309 __PACKAGE__->register_method(
310     method => 'fetch_transit_by_copy',
311     api_name => 'open-ils.circ.fetch_transit_by_copy',
312 );
313
314 sub fetch_transit_by_copy {
315     my( $self, $conn, $auth, $copyid ) = @_;
316     my $e = new_editor(authtoken=>$auth);
317     return $e->event unless $e->checkauth;
318     my $t = $e->search_action_transit_copy(
319         {
320             target_copy => $copyid,
321             dest_recv_time => undef,
322             cancel_time => undef
323         }
324     )->[0];
325     return $e->event unless $t;
326     my $ht = $e->retrieve_action_hold_transit_copy($t->id);
327     return { atc => $t, ahtc => $ht };
328 }
329
330
331
332 __PACKAGE__->register_method(
333     method => 'transits_by_lib',
334     api_name => 'open-ils.circ.transit.retrieve_by_lib',
335 );
336
337
338 # start_date and end_date are optional endpoints for the transit creation date
339 sub transits_by_lib {
340     my( $self, $conn, $auth, $orgid, $start_date, $end_date ) = @_;
341     my $e = new_editor(authtoken=>$auth);
342     return $e->event unless $e->checkauth;
343     return $e->event unless $e->allowed('VIEW_CIRCULATIONS'); # eh.. basically the same permission
344
345     my $order_by = {order_by => { atc => 'source_send_time' }};
346     my $search = { dest_recv_time => undef, cancel_time => undef };
347
348     if($end_date) {
349         if($start_date) {
350             $search->{source_send_time} = {between => [$start_date, $end_date]};
351         } else {
352             $search->{source_send_time} = {'<=' => $end_date};
353         }
354     } elsif($start_date) {
355         $search->{source_send_time} = {'>=' => $start_date};
356     }
357
358     $search->{dest} = $orgid;
359
360     my $tos = $e->search_action_transit_copy([ $search, $order_by ], {idlist=>1});
361
362     delete $$search{dest};
363     $search->{source} = $orgid;
364
365     my $froms = $e->search_action_transit_copy([ $search, $order_by ], {idlist=>1});
366
367     return { from => $froms, to => $tos };
368 }
369
370
371
372 __PACKAGE__->register_method(
373     method => 'fetch_transit',
374     api_name => 'open-ils.circ.transit.retrieve',
375 );
376 sub fetch_transit {
377     my( $self, $conn, $auth, $transid ) = @_;
378     my $e = new_editor(authtoken=>$auth);
379     return $e->event unless $e->checkauth;
380     return $e->event unless $e->allowed('VIEW_CIRCULATIONS'); # eh.. basically the same permission
381
382     my $ht = $e->retrieve_action_hold_transit_copy($transid);
383     return $ht if $ht;
384
385     my $t = $e->retrieve_action_transit_copy($transid)
386         or return $e->event;
387     return $t;
388 }
389
390     
391
392
393
394
395 1;