]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Circ.pm
return fleshed circ object in damaged event
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Circ.pm
1 package OpenILS::Application::Circ;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
5
6 use OpenILS::Application::Circ::Circulate;
7 use OpenILS::Application::Circ::Survey;
8 use OpenILS::Application::Circ::StatCat;
9 use OpenILS::Application::Circ::Holds;
10 use OpenILS::Application::Circ::HoldNotify;
11 use OpenILS::Application::Circ::Money;
12 use OpenILS::Application::Circ::NonCat;
13 use OpenILS::Application::Circ::CopyLocations;
14 use OpenILS::Application::Circ::CircCommon;
15
16 use DateTime;
17 use DateTime::Format::ISO8601;
18
19 use OpenILS::Application::AppUtils;
20
21 use OpenSRF::Utils qw/:datetime/;
22 use OpenILS::Utils::ModsParser;
23 use OpenILS::Event;
24 use OpenSRF::EX qw(:try);
25 use OpenSRF::Utils::Logger qw(:logger);
26 use OpenILS::Utils::Fieldmapper;
27 use OpenILS::Utils::Editor;
28 use OpenILS::Utils::CStoreEditor q/:funcs/;
29 use OpenILS::Const qw/:const/;
30 use OpenSRF::Utils::SettingsClient;
31 use OpenILS::Application::Cat::AssetCommon;
32
33 my $apputils = "OpenILS::Application::AppUtils";
34 my $U = $apputils;
35
36
37 # ------------------------------------------------------------------------
38 # Top level Circ package;
39 # ------------------------------------------------------------------------
40
41 sub initialize {
42         my $self = shift;
43         OpenILS::Application::Circ::Circulate->initialize();
44 }
45
46
47 __PACKAGE__->register_method(
48         method => 'retrieve_circ',
49         api_name        => 'open-ils.circ.retrieve',
50         signature => q/
51                 Retrieve a circ object by id
52                 @param authtoken Login session key
53                 @pararm circid The id of the circ object
54         /
55 );
56 sub retrieve_circ {
57         my( $s, $c, $a, $i ) = @_;
58         my $e = new_editor(authtoken => $a);
59         return $e->event unless $e->checkauth;
60         my $circ = $e->retrieve_action_circulation($i) or return $e->event;
61         if( $e->requestor->id ne $circ->usr ) {
62                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
63         }
64         return $circ;
65 }
66
67
68 __PACKAGE__->register_method(
69         method => 'fetch_circ_mods',
70         api_name => 'open-ils.circ.circ_modifier.retrieve.all');
71 sub fetch_circ_mods {
72     my($self, $conn, $args) = @_;
73     my $mods = new_editor()->retrieve_all_config_circ_modifier;
74     return [ map {$_->code} @$mods ] unless $$args{full};
75     return $mods;
76 }
77
78 __PACKAGE__->register_method(
79         method => 'fetch_bill_types',
80         api_name => 'open-ils.circ.billing_type.retrieve.all');
81 sub fetch_bill_types {
82         my $conf = OpenSRF::Utils::SettingsClient->new;
83         return $conf->config_value(
84                 'apps', 'open-ils.circ', 'app_settings', 'billing_types', 'type' );
85 }
86
87
88 __PACKAGE__->register_method(
89     method => 'ranged_billing_types',
90     api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
91
92 sub ranged_billing_types {
93     my($self, $conn, $auth, $org_id, $depth) = @_;
94     my $e = new_editor(authtoken => $auth);
95     return $e->event unless $e->checkauth;
96     return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
97     return $e->search_config_billing_type(
98         {owner => $U->get_org_full_path($org_id, $depth)});
99 }
100
101
102
103 # ------------------------------------------------------------------------
104 # Returns an array of {circ, record} hashes checked out by the user.
105 # ------------------------------------------------------------------------
106 __PACKAGE__->register_method(
107         method  => "checkouts_by_user",
108         api_name        => "open-ils.circ.actor.user.checked_out",
109         NOTES           => <<"  NOTES");
110         Returns a list of open circulations as a pile of objects.  Each object
111         contains the relevant copy, circ, and record
112         NOTES
113
114 sub checkouts_by_user {
115         my( $self, $client, $user_session, $user_id ) = @_;
116
117         my( $requestor, $target, $copy, $record, $evt );
118
119         ( $requestor, $target, $evt ) = 
120                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
121         return $evt if $evt;
122
123         my $circs = $apputils->simplereq(
124                 'open-ils.cstore',
125                 "open-ils.cstore.direct.action.open_circulation.search.atomic", 
126                 { usr => $target->id, checkin_time => undef } );
127 #               { usr => $target->id } );
128
129         my @results;
130         for my $circ (@$circs) {
131
132                 ( $copy, $evt )  = $apputils->fetch_copy($circ->target_copy);
133                 return $evt if $evt;
134
135                 $logger->debug("Retrieving record for copy " . $circ->target_copy);
136
137                 ($record, $evt) = $apputils->fetch_record_by_copy( $circ->target_copy );
138                 return $evt if $evt;
139
140                 my $mods = $apputils->record_to_mvr($record);
141
142                 push( @results, { copy => $copy, circ => $circ, record => $mods } );
143         }
144
145         return \@results;
146
147 }
148
149
150
151 __PACKAGE__->register_method(
152         method  => "checkouts_by_user_slim",
153         api_name        => "open-ils.circ.actor.user.checked_out.slim",
154         NOTES           => <<"  NOTES");
155         Returns a list of open circulation objects
156         NOTES
157
158 # DEPRECAT ME?? XXX
159 sub checkouts_by_user_slim {
160         my( $self, $client, $user_session, $user_id ) = @_;
161
162         my( $requestor, $target, $copy, $record, $evt );
163
164         ( $requestor, $target, $evt ) = 
165                 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
166         return $evt if $evt;
167
168         $logger->debug( 'User ' . $requestor->id . 
169                 " retrieving checked out items for user " . $target->id );
170
171         # XXX Make the call correct..
172         return $apputils->simplereq(
173                 'open-ils.cstore',
174                 "open-ils.cstore.direct.action.open_circulation.search.atomic", 
175                 { usr => $target->id, checkin_time => undef } );
176 #               { usr => $target->id } );
177 }
178
179
180 __PACKAGE__->register_method(
181         method  => "checkouts_by_user_opac",
182         api_name        => "open-ils.circ.actor.user.checked_out.opac",);
183
184 # XXX Deprecate Me
185 sub checkouts_by_user_opac {
186         my( $self, $client, $auth, $user_id ) = @_;
187
188         my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
189         return $e->event unless $e->checkauth;
190         $user_id ||= $e->requestor->id;
191         return $e->event unless 
192                 my $patron = $e->retrieve_actor_user($user_id);
193
194         my $data;
195         my $search = {usr => $user_id, stop_fines => undef};
196
197         if( $user_id ne $e->requestor->id ) {
198                 $data = $e->search_action_circulation(
199                         $search, {checkperm=>1, permorg=>$patron->home_ou})
200                         or return $e->event;
201
202         } else {
203                 $data = $e->search_action_circulation($search);
204         }
205
206         return $data;
207 }
208
209
210 __PACKAGE__->register_method(
211         method  => "title_from_transaction",
212         api_name        => "open-ils.circ.circ_transaction.find_title",
213         NOTES           => <<"  NOTES");
214         Returns a mods object for the title that is linked to from the 
215         copy from the hold that created the given transaction
216         NOTES
217
218 sub title_from_transaction {
219         my( $self, $client, $login_session, $transactionid ) = @_;
220
221         my( $user, $circ, $title, $evt );
222
223         ( $user, $evt ) = $apputils->checkses( $login_session );
224         return $evt if $evt;
225
226         ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
227         return $evt if $evt;
228         
229         ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
230         return $evt if $evt;
231
232         return $apputils->record_to_mvr($title);
233 }
234
235
236
237 __PACKAGE__->register_method(
238         method  => "new_set_circ_lost",
239         api_name        => "open-ils.circ.circulation.set_lost",
240         signature       => q/
241         Sets the copy and related open circulation to lost
242                 @param auth
243                 @param args : barcode
244         /
245 );
246
247
248 # ---------------------------------------------------------------------
249 # Sets a circulation to lost.  updates copy status to lost
250 # applies copy and/or prcoessing fees depending on org settings
251 # ---------------------------------------------------------------------
252 sub new_set_circ_lost {
253     my( $self, $conn, $auth, $args ) = @_;
254
255     my $e = new_editor(authtoken=>$auth, xact=>1);
256     return $e->die_event unless $e->checkauth;
257
258     my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
259         or return $e->die_event;
260
261     my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
262     return $evt if $evt;
263
264     $e->commit;
265     return 1;
266 }
267
268
269 __PACKAGE__->register_method(
270         method  => "set_circ_claims_returned",
271         api_name        => "open-ils.circ.circulation.set_claims_returned",
272         signature       => q/
273         Sets the circ for the given item as claims returned
274         If a backdate is provided, overdue fines will be voided
275         back to the backdate
276                 @param auth
277                 @param args : barcode, backdate
278         /
279 );
280
281 sub set_circ_claims_returned {
282     my( $self, $conn, $auth, $args ) = @_;
283
284     my $e = new_editor(authtoken=>$auth, xact=>1);
285     return $e->die_event unless $e->checkauth;
286
287     my $barcode = $$args{barcode};
288     my $backdate = $$args{backdate};
289
290     $logger->info("marking circ for item $barcode as claims returned".
291         (($backdate) ? " with backdate $backdate" : ''));
292
293     my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0] 
294         or return $e->die_event;
295
296     my $circ = $e->search_action_circulation(
297         {checkin_time => undef, target_copy => $copy->id})->[0]
298             or return $e->die_event;
299
300     $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib) 
301         or return $e->die_event;
302
303     $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
304         $circ->stop_fines_time('now') unless $circ->stop_fines_time;
305
306     if( $backdate ) {
307         # make it look like the circ stopped at the cliams returned time
308         $circ->stop_fines_time(clense_ISO8601($backdate));
309         my $evt = OpenILS::Application::Circ::CircCommon->void_overdues($e, $circ, $backdate);
310         return $evt if $evt;
311     }
312
313     $e->update_action_circulation($circ) or return $e->die_event;
314     $e->commit;
315     return 1;
316 }
317
318
319
320
321
322 __PACKAGE__->register_method (
323         method          => 'set_circ_due_date',
324         api_name                => 'open-ils.circ.circulation.due_date.update',
325         signature       => q/
326                 Updates the due_date on the given circ
327                 @param authtoken
328                 @param circid The id of the circ to update
329                 @param date The timestamp of the new due date
330         /
331 );
332
333 sub set_circ_due_date {
334         my( $self, $conn, $auth, $circ_id, $date ) = @_;
335
336     my $e = new_editor(xact=>1, authtoken=>$auth);
337     return $e->die_event unless $e->checkauth;
338     my $circ = $e->retrieve_action_circulation($circ_id)
339         or return $e->die_event;
340
341     return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
342         $date = clense_ISO8601($date);
343         $circ->due_date($date);
344     $e->update_action_circulation($circ) or return $e->die_event;
345     $e->commit;
346
347     return $circ->id;
348 }
349
350
351 __PACKAGE__->register_method(
352         method          => "create_in_house_use",
353         api_name                => 'open-ils.circ.in_house_use.create',
354         signature       =>      q/
355                 Creates an in-house use action.
356                 @param $authtoken The login session key
357                 @param params A hash of params including
358                         'location' The org unit id where the in-house use occurs
359                         'copyid' The copy in question
360                         'count' The number of in-house uses to apply to this copy
361                 @return An array of id's representing the id's of the newly created
362                 in-house use objects or an event on an error
363         /);
364
365 __PACKAGE__->register_method(
366         method          => "create_in_house_use",
367         api_name                => 'open-ils.circ.non_cat_in_house_use.create',
368 );
369
370
371 sub create_in_house_use {
372         my( $self, $client, $auth, $params ) = @_;
373
374         my( $evt, $copy );
375         my $org                 = $params->{location};
376         my $copyid              = $params->{copyid};
377         my $count               = $params->{count} || 1;
378         my $nc_type             = $params->{non_cat_type};
379         my $use_time    = $params->{use_time} || 'now';
380
381         my $e = new_editor(xact=>1,authtoken=>$auth);
382         return $e->event unless $e->checkauth;
383         return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
384
385         my $non_cat = 1 if $self->api_name =~ /non_cat/;
386
387         unless( $non_cat ) {
388                 if( $copyid ) {
389                         $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
390                 } else {
391                         $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
392                                 or return $e->event;
393                         $copyid = $copy->id;
394                 }
395         }
396
397         if( $use_time ne 'now' ) {
398                 $use_time = clense_ISO8601($use_time);
399                 $logger->debug("in_house_use setting use time to $use_time");
400         }
401
402         my @ids;
403         for(1..$count) {
404
405                 my $ihu;
406                 my $method;
407                 my $cmeth;
408
409                 if($non_cat) {
410                         $ihu = Fieldmapper::action::non_cat_in_house_use->new;
411                         $ihu->item_type($nc_type);
412                         $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
413                         $cmeth = "create_action_non_cat_in_house_use";
414
415                 } else {
416                         $ihu = Fieldmapper::action::in_house_use->new;
417                         $ihu->item($copyid);
418                         $method = 'open-ils.storage.direct.action.in_house_use.create';
419                         $cmeth = "create_action_in_house_use";
420                 }
421
422                 $ihu->staff($e->requestor->id);
423                 $ihu->org_unit($org);
424                 $ihu->use_time($use_time);
425
426                 $ihu = $e->$cmeth($ihu) or return $e->event;
427                 push( @ids, $ihu->id );
428         }
429
430         $e->commit;
431         return \@ids;
432 }
433
434
435
436
437
438 __PACKAGE__->register_method(
439         method  => "view_circs",
440         api_name        => "open-ils.circ.copy_checkout_history.retrieve",
441         notes           => q/
442                 Retrieves the last X circs for a given copy
443                 @param authtoken The login session key
444                 @param copyid The copy to check
445                 @param count How far to go back in the item history
446                 @return An array of circ ids
447         /);
448
449 # ----------------------------------------------------------------------
450 # Returns $count most recent circs.  If count exceeds the configured 
451 # max, use the configured max instead
452 # ----------------------------------------------------------------------
453 sub view_circs {
454         my( $self, $client, $authtoken, $copyid, $count ) = @_; 
455
456     my $e = new_editor(authtoken => $authtoken);
457     return $e->event unless $e->checkauth;
458     
459     my $copy = $e->retrieve_asset_copy([
460         $copyid,
461         {   flesh => 1,
462             flesh_fields => {acp => ['call_number']}
463         }
464     ]) or return $e->event;
465
466     return $e->event unless $e->allowed(
467         'VIEW_COPY_CHECKOUT_HISTORY', 
468         ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ? 
469             $copy->circ_lib : $copy->call_number->owning_lib);
470         
471     my $max_history = $U->ou_ancestor_setting_value(
472         $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
473
474     if(defined $max_history) {
475         $count = $max_history unless defined $count and $count < $max_history;
476     } else {
477         $count = 4 unless defined $count;
478     }
479
480     return $e->search_action_circulation([
481         {target_copy => $copyid}, 
482         {limit => $count, order_by => { circ => "xact_start DESC" }} 
483     ]);
484 }
485
486
487 __PACKAGE__->register_method(
488         method  => "circ_count",
489         api_name        => "open-ils.circ.circulation.count",
490         notes           => q/
491                 Returns the number of times the item has circulated
492                 @param copyid The copy to check
493         /);
494
495 sub circ_count {
496         my( $self, $client, $copyid, $range ) = @_; 
497         my $e = OpenILS::Utils::Editor->new;
498         return $e->request('open-ils.storage.asset.copy.circ_count', $copyid, $range);
499 }
500
501
502
503 __PACKAGE__->register_method(
504         method          => 'fetch_notes',
505         api_name                => 'open-ils.circ.copy_note.retrieve.all',
506         signature       => q/
507                 Returns an array of copy note objects.  
508                 @param args A named hash of parameters including:
509                         authtoken       : Required if viewing non-public notes
510                         itemid          : The id of the item whose notes we want to retrieve
511                         pub                     : True if all the caller wants are public notes
512                 @return An array of note objects
513         /);
514
515 __PACKAGE__->register_method(
516         method          => 'fetch_notes',
517         api_name                => 'open-ils.circ.call_number_note.retrieve.all',
518         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
519
520 __PACKAGE__->register_method(
521         method          => 'fetch_notes',
522         api_name                => 'open-ils.circ.title_note.retrieve.all',
523         signature       => q/@see open-ils.circ.copy_note.retrieve.all/);
524
525
526 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
527 sub fetch_notes {
528         my( $self, $connection, $args ) = @_;
529
530         my $id = $$args{itemid};
531         my $authtoken = $$args{authtoken};
532         my( $r, $evt);
533
534         if( $self->api_name =~ /copy/ ) {
535                 if( $$args{pub} ) {
536                         return $U->cstorereq(
537                                 'open-ils.cstore.direct.asset.copy_note.search.atomic',
538                                 { owning_copy => $id, pub => 't' } );
539                 } else {
540                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
541                         return $evt if $evt;
542                         return $U->cstorereq(
543                                 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
544                 }
545
546         } elsif( $self->api_name =~ /call_number/ ) {
547                 if( $$args{pub} ) {
548                         return $U->cstorereq(
549                                 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
550                                 { call_number => $id, pub => 't' } );
551                 } else {
552                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
553                         return $evt if $evt;
554                         return $U->cstorereq(
555                                 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
556                 }
557
558         } elsif( $self->api_name =~ /title/ ) {
559                 if( $$args{pub} ) {
560                         return $U->cstorereq(
561                                 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
562                                 { record => $id, pub => 't' } );
563                 } else {
564                         ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
565                         return $evt if $evt;
566                         return $U->cstorereq(
567                                 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
568                 }
569         }
570
571         return undef;
572 }
573
574 __PACKAGE__->register_method(
575         method  => 'has_notes',
576         api_name        => 'open-ils.circ.copy.has_notes');
577 __PACKAGE__->register_method(
578         method  => 'has_notes',
579         api_name        => 'open-ils.circ.call_number.has_notes');
580 __PACKAGE__->register_method(
581         method  => 'has_notes',
582         api_name        => 'open-ils.circ.title.has_notes');
583
584
585 sub has_notes {
586         my( $self, $conn, $authtoken, $id ) = @_;
587         my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
588         return $editor->event unless $editor->checkauth;
589
590         my $n = $editor->search_asset_copy_note(
591                 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
592
593         $n = $editor->search_asset_call_number_note(
594                 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
595
596         $n = $editor->search_biblio_record_note(
597                 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
598
599         return scalar @$n;
600 }
601
602
603
604 __PACKAGE__->register_method(
605         method          => 'create_copy_note',
606         api_name                => 'open-ils.circ.copy_note.create',
607         signature       => q/
608                 Creates a new copy note
609                 @param authtoken The login session key
610                 @param note     The note object to create
611                 @return The id of the new note object
612         /);
613
614 sub create_copy_note {
615         my( $self, $connection, $authtoken, $note ) = @_;
616
617         my $e = new_editor(xact=>1, authtoken=>$authtoken);
618         return $e->event unless $e->checkauth;
619         my $copy = $e->retrieve_asset_copy(
620                 [
621                         $note->owning_copy,
622                         {       flesh => 1,
623                                 flesh_fields => { 'acp' => ['call_number'] }
624                         }
625                 ]
626         );
627
628         return $e->event unless 
629                 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
630
631         $note->create_date('now');
632         $note->creator($e->requestor->id);
633         $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
634         $note->clear_id;
635
636         $e->create_asset_copy_note($note) or return $e->event;
637         $e->commit;
638         return $note->id;
639 }
640
641
642 __PACKAGE__->register_method(
643         method          => 'delete_copy_note',
644         api_name                =>      'open-ils.circ.copy_note.delete',
645         signature       => q/
646                 Deletes an existing copy note
647                 @param authtoken The login session key
648                 @param noteid The id of the note to delete
649                 @return 1 on success - Event otherwise.
650                 /);
651 sub delete_copy_note {
652         my( $self, $conn, $authtoken, $noteid ) = @_;
653
654         my $e = new_editor(xact=>1, authtoken=>$authtoken);
655         return $e->die_event unless $e->checkauth;
656
657         my $note = $e->retrieve_asset_copy_note([
658                 $noteid,
659                 { flesh => 2,
660                         flesh_fields => {
661                                 'acpn' => [ 'owning_copy' ],
662                                 'acp' => [ 'call_number' ],
663                         }
664                 }
665         ]) or return $e->die_event;
666
667         if( $note->creator ne $e->requestor->id ) {
668                 return $e->die_event unless 
669                         $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
670         }
671
672         $e->delete_asset_copy_note($note) or return $e->die_event;
673         $e->commit;
674         return 1;
675 }
676
677
678 __PACKAGE__->register_method(
679         method => 'age_hold_rules',
680         api_name        =>  'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
681 );
682
683 sub age_hold_rules {
684         my( $self, $conn ) = @_;
685         return new_editor()->retrieve_all_config_rules_age_hold_protect();
686 }
687
688
689
690 __PACKAGE__->register_method(
691         method => 'copy_details_barcode',
692     authoritative => 1,
693         api_name => 'open-ils.circ.copy_details.retrieve.barcode');
694 sub copy_details_barcode {
695         my( $self, $conn, $auth, $barcode ) = @_;
696     my $e = new_editor();
697     my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
698     return $e->event unless $cid;
699         return copy_details( $self, $conn, $auth, $cid );
700 }
701
702
703 __PACKAGE__->register_method(
704         method => 'copy_details',
705         api_name => 'open-ils.circ.copy_details.retrieve');
706
707 sub copy_details {
708         my( $self, $conn, $auth, $copy_id ) = @_;
709         my $e = new_editor(authtoken=>$auth);
710         return $e->event unless $e->checkauth;
711
712         my $flesh = { flesh => 1 };
713
714         my $copy = $e->retrieve_asset_copy(
715                 [
716                         $copy_id,
717                         {
718                                 flesh => 2,
719                                 flesh_fields => {
720                                         acp => ['call_number'],
721                                         acn => ['record']
722                                 }
723                         }
724                 ]) or return $e->event;
725
726
727         # De-flesh the copy for backwards compatibility
728         my $mvr;
729         my $vol = $copy->call_number;
730         if( ref $vol ) {
731                 $copy->call_number($vol->id);
732                 my $record = $vol->record;
733                 if( ref $record ) {
734                         $vol->record($record->id);
735                         $mvr = $U->record_to_mvr($record);
736                 }
737         }
738
739
740         my $hold = $e->search_action_hold_request(
741                 { 
742                         current_copy            => $copy_id, 
743                         capture_time            => { "!=" => undef },
744                         fulfillment_time        => undef,
745                         cancel_time                     => undef,
746                 }
747         )->[0];
748
749         OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
750
751         my $transit = $e->search_action_transit_copy(
752                 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
753
754         # find the latest circ, open or closed
755         my $circ = $e->search_action_circulation(
756                 [
757                         { target_copy => $copy_id },
758                         { order_by => { circ => 'xact_start desc' }, limit => 1 }
759                 ]
760         )->[0];
761
762
763         return {
764                 copy            => $copy,
765                 hold            => $hold,
766                 transit => $transit,
767                 circ            => $circ,
768                 volume  => $vol,
769                 mvr             => $mvr,
770         };
771 }
772
773
774
775
776 __PACKAGE__->register_method(
777         method => 'mark_item',
778         api_name => 'open-ils.circ.mark_item_damaged',
779         signature       => q/
780                 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
781                 @param authtoken The login session key
782                 @param copy_id The ID of the copy to mark as damaged
783                 @return 1 on success - Event otherwise.
784                 /
785 );
786 __PACKAGE__->register_method(
787         method => 'mark_item',
788         api_name => 'open-ils.circ.mark_item_missing',
789         signature       => q/
790                 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
791                 @param authtoken The login session key
792                 @param copy_id The ID of the copy to mark as missing 
793                 @return 1 on success - Event otherwise.
794                 /
795 );
796 __PACKAGE__->register_method(
797         method => 'mark_item',
798         api_name => 'open-ils.circ.mark_item_bindery',
799         signature       => q/
800                 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
801                 @param authtoken The login session key
802                 @param copy_id The ID of the copy to mark as bindery
803                 @return 1 on success - Event otherwise.
804                 /
805 );
806 __PACKAGE__->register_method(
807         method => 'mark_item',
808         api_name => 'open-ils.circ.mark_item_on_order',
809         signature       => q/
810                 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
811                 @param authtoken The login session key
812                 @param copy_id The ID of the copy to mark as on order 
813                 @return 1 on success - Event otherwise.
814                 /
815 );
816 __PACKAGE__->register_method(
817         method => 'mark_item',
818         api_name => 'open-ils.circ.mark_item_ill',
819         signature       => q/
820                 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
821                 @param authtoken The login session key
822                 @param copy_id The ID of the copy to mark as inter-library loan
823                 @return 1 on success - Event otherwise.
824                 /
825 );
826 __PACKAGE__->register_method(
827         method => 'mark_item',
828         api_name => 'open-ils.circ.mark_item_cataloging',
829         signature       => q/
830                 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
831                 @param authtoken The login session key
832                 @param copy_id The ID of the copy to mark as cataloging 
833                 @return 1 on success - Event otherwise.
834                 /
835 );
836 __PACKAGE__->register_method(
837         method => 'mark_item',
838         api_name => 'open-ils.circ.mark_item_reserves',
839         signature       => q/
840                 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
841                 @param authtoken The login session key
842                 @param copy_id The ID of the copy to mark as reserves
843                 @return 1 on success - Event otherwise.
844                 /
845 );
846 __PACKAGE__->register_method(
847         method => 'mark_item',
848         api_name => 'open-ils.circ.mark_item_discard',
849         signature       => q/
850                 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
851                 @param authtoken The login session key
852                 @param copy_id The ID of the copy to mark as discard
853                 @return 1 on success - Event otherwise.
854                 /
855 );
856
857 sub mark_item {
858         my( $self, $conn, $auth, $copy_id, $args ) = @_;
859         my $e = new_editor(authtoken=>$auth, xact =>1);
860         return $e->die_event unless $e->checkauth;
861     $args ||= {};
862
863     my $copy = $e->retrieve_asset_copy([
864         $copy_id,
865         {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
866             or return $e->die_event;
867
868     my $owning_lib = 
869         ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ? 
870             $copy->circ_lib : $copy->call_number->owning_lib;
871
872     return $e->die_event unless $e->allowed('UPDATE_COPY', $owning_lib);
873
874
875         my $perm = 'MARK_ITEM_MISSING';
876         my $stat = OILS_COPY_STATUS_MISSING;
877
878         if( $self->api_name =~ /damaged/ ) {
879                 $perm = 'MARK_ITEM_DAMAGED';
880                 $stat = OILS_COPY_STATUS_DAMAGED;
881         my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
882         return $evt if $evt;
883
884         } elsif ( $self->api_name =~ /bindery/ ) {
885                 $perm = 'MARK_ITEM_BINDERY';
886                 $stat = OILS_COPY_STATUS_BINDERY;
887         } elsif ( $self->api_name =~ /on_order/ ) {
888                 $perm = 'MARK_ITEM_ON_ORDER';
889                 $stat = OILS_COPY_STATUS_ON_ORDER;
890         } elsif ( $self->api_name =~ /ill/ ) {
891                 $perm = 'MARK_ITEM_ILL';
892                 $stat = OILS_COPY_STATUS_ILL;
893         } elsif ( $self->api_name =~ /cataloging/ ) {
894                 $perm = 'MARK_ITEM_CATALOGING';
895                 $stat = OILS_COPY_STATUS_CATALOGING;
896         } elsif ( $self->api_name =~ /reserves/ ) {
897                 $perm = 'MARK_ITEM_RESERVES';
898                 $stat = OILS_COPY_STATUS_RESERVES;
899         } elsif ( $self->api_name =~ /discard/ ) {
900                 $perm = 'MARK_ITEM_DISCARD';
901                 $stat = OILS_COPY_STATUS_DISCARD;
902         }
903
904
905         $copy->status($stat);
906         $copy->edit_date('now');
907         $copy->editor($e->requestor->id);
908
909         $e->update_asset_copy($copy) or return $e->die_event;
910
911         my $holds = $e->search_action_hold_request(
912                 { 
913                         current_copy => $copy->id,
914                         fulfillment_time => undef,
915                         cancel_time => undef,
916                 }
917         );
918
919         $e->commit;
920
921         $logger->debug("resetting holds that target the marked copy");
922         OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
923
924         return 1;
925 }
926
927 sub handle_mark_damaged {
928     my($e, $copy, $owning_lib, $args) = @_;
929
930     my $apply = $args->{apply_fines} || '';
931     return undef if $apply eq 'noapply';
932
933     # grab the last circulation
934     my $circ = $e->search_action_circulation([
935         {   target_copy => $copy->id}, 
936         {   limit => 1, 
937             order_by => {circ => "xact_start DESC"},
938             flesh => 1,
939             flesh_fields => {circ => ['target_copy', 'usr']}
940         }
941     ])->[0];
942
943     return undef unless $circ;
944
945     my $charge_price = $U->ou_ancestor_setting_value(
946         $owning_lib, 'circ.charge_on_damaged', $e);
947
948     my $proc_fee = $U->ou_ancestor_setting_value(
949         $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
950
951     return undef unless $charge_price or $proc_fee;
952
953     my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
954     my $total = $copy_price + $proc_fee;
955
956     if($apply) {
957         
958         if($charge_price and $copy_price) {
959             my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
960                 $e, $copy_price, 7, 'Damaged Item', $circ->id);
961             return $evt if $evt;
962         }
963
964         if($proc_fee) {
965             my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
966                 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
967             return $evt if $evt;
968         }
969
970         my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
971         return $evt if $evt;
972         return undef;
973
974     } else {
975         return OpenILS::Event->new('DAMAGE_CHARGE', 
976             payload => {
977                 circ => $circ,
978                 charge => $total
979             }
980         );
981     }
982 }
983
984
985
986
987
988
989 # ----------------------------------------------------------------------
990 __PACKAGE__->register_method(
991         method => 'magic_fetch',
992         api_name => 'open-ils.agent.fetch'
993 );
994
995 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
996
997 sub magic_fetch {
998         my( $self, $conn, $auth, $args ) = @_;
999         my $e = new_editor( authtoken => $auth );
1000         return $e->event unless $e->checkauth;
1001
1002         my $hint = $$args{hint};
1003         my $id  = $$args{id};
1004
1005         # Is the call allowed to fetch this type of object?
1006         return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1007
1008         # Find the class the implements the given hint
1009         my ($class) = grep { 
1010                 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1011
1012         $class =~ s/Fieldmapper:://og;
1013         $class =~ s/::/_/og;
1014         my $method = "retrieve_$class";
1015
1016         my $obj = $e->$method($id) or return $e->event;
1017         return $obj;
1018 }
1019 # ----------------------------------------------------------------------
1020
1021
1022 __PACKAGE__->register_method(
1023         method  => "fleshed_circ_retrieve",
1024     authoritative => 1,
1025         api_name        => "open-ils.circ.fleshed.retrieve",);
1026
1027 sub fleshed_circ_retrieve {
1028         my( $self, $client, $id ) = @_;
1029         my $e = new_editor();
1030         my $circ = $e->retrieve_action_circulation(
1031                 [
1032                         $id,
1033                         { 
1034                                 flesh                           => 4,
1035                                 flesh_fields    => { 
1036                                         circ => [ qw/ target_copy / ],
1037                                         acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number / ],
1038                                         ascecm => [ qw/ stat_cat stat_cat_entry / ],
1039                                         acn => [ qw/ record / ],
1040                                 }
1041                         }
1042                 ]
1043         ) or return $e->event;
1044         
1045         my $copy = $circ->target_copy;
1046         my $vol = $copy->call_number;
1047         my $rec = $circ->target_copy->call_number->record;
1048
1049         $vol->record($rec->id);
1050         $copy->call_number($vol->id);
1051         $circ->target_copy($copy->id);
1052
1053         my $mvr;
1054
1055         if( $rec->id == OILS_PRECAT_RECORD ) {
1056                 $rec = undef;
1057                 $vol = undef;
1058         } else { 
1059                 $mvr = $U->record_to_mvr($rec);
1060                 $rec->marc(''); # drop the bulky marc data
1061         }
1062
1063         return {
1064                 circ => $circ,
1065                 copy => $copy,
1066                 volume => $vol,
1067                 record => $rec,
1068                 mvr => $mvr,
1069         };
1070 }
1071
1072 # {"select":{"acp":["id"],"circ":[{"aggregate":true,"transform":"count","alias":"count","column":"id"}]},"from":{"acp":{"circ":{"field":"target_copy","fkey":"id","type":"left"},"acn"{"field":"id","fkey":"call_number"}}},"where":{"+acn":{"record":200057}}
1073
1074
1075 1;