1 package OpenILS::Application::Circ;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
5 use OpenILS::Application::Circ::Circulate;
6 use OpenILS::Application::Circ::Survey;
7 use OpenILS::Application::Circ::StatCat;
8 use OpenILS::Application::Circ::Holds;
9 use OpenILS::Application::Circ::Money;
10 use OpenILS::Application::Circ::NonCat;
11 use OpenILS::Application::Circ::CopyLocations;
14 use DateTime::Format::ISO8601;
16 use OpenILS::Application::AppUtils;
17 my $apputils = "OpenILS::Application::AppUtils";
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenILS::Utils::ModsParser;
22 use OpenSRF::EX qw(:try);
23 use OpenSRF::Utils::Logger qw(:logger);
24 use OpenILS::Utils::Fieldmapper;
25 use OpenILS::Utils::Editor;
26 #my $logger = "OpenSRF::Utils::Logger";
29 # ------------------------------------------------------------------------
30 # Top level Circ package;
31 # ------------------------------------------------------------------------
35 OpenILS::Application::Circ::Circulate->initialize();
39 __PACKAGE__->register_method(
40 method => 'retrieve_circ',
41 api_name => 'open-ils.circ.retrieve',
43 Retrieve a circ object by id
44 @param authtoken Login session key
45 @pararm circid The id of the circ object
49 my( $s, $c, $a, $i ) = @_;
50 my($reqr, $evt) = $U->checksesperm($a, 'VIEW_CIRCULATIONS');
53 ($circ, $evt) = $U->fetch_circulation($i);
59 # ------------------------------------------------------------------------
60 # Returns an array of {circ, record} hashes checked out by the user.
61 # ------------------------------------------------------------------------
62 __PACKAGE__->register_method(
63 method => "checkouts_by_user",
64 api_name => "open-ils.circ.actor.user.checked_out",
66 Returns a list of open circulations as a pile of objects. each object
67 contains the relevant copy, circ, and record
70 sub checkouts_by_user {
71 my( $self, $client, $user_session, $user_id ) = @_;
73 my( $requestor, $target, $copy, $record, $evt );
75 ( $requestor, $target, $evt ) =
76 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
79 my $circs = $apputils->simplereq(
81 "open-ils.storage.direct.action.open_circulation.search.atomic",
82 { usr => $target->id, checkin_time => undef } );
83 # { usr => $target->id } );
86 for my $circ (@$circs) {
88 ( $copy, $evt ) = $apputils->fetch_copy($circ->target_copy);
91 $logger->debug("Retrieving record for copy " . $circ->target_copy);
93 ($record, $evt) = $apputils->fetch_record_by_copy( $circ->target_copy );
96 my $mods = $apputils->record_to_mvr($record);
98 push( @results, { copy => $copy, circ => $circ, record => $mods } );
107 __PACKAGE__->register_method(
108 method => "checkouts_by_user_slim",
109 api_name => "open-ils.circ.actor.user.checked_out.slim",
110 NOTES => <<" NOTES");
111 Returns a list of open circulation objects
114 sub checkouts_by_user_slim {
115 my( $self, $client, $user_session, $user_id ) = @_;
117 my( $requestor, $target, $copy, $record, $evt );
119 ( $requestor, $target, $evt ) =
120 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
123 $logger->debug( 'User ' . $requestor->id .
124 " retrieving checked out items for user " . $target->id );
126 # XXX Make the call correct..
127 return $apputils->simplereq(
129 "open-ils.storage.direct.action.open_circulation.search.atomic",
130 { usr => $target->id, checkin_time => undef } );
131 # { usr => $target->id } );
135 __PACKAGE__->register_method(
136 method => "checkouts_by_user_opac",
137 api_name => "open-ils.circ.actor.user.checked_out.opac",);
139 sub checkouts_by_user_opac {
140 my( $self, $client, $auth, $user_id ) = @_;
142 my $e = OpenILS::Utils::Editor->new( authtoken => $auth );
143 return $e->event unless $e->checkauth;
144 $user_id ||= $e->requestor->id;
145 return $e->event unless
146 my $patron = $e->retrieve_actor_user($user_id);
149 my $search = {usr => $user_id, stop_fines => undef};
151 if( $user_id ne $e->requestor->id ) {
152 $data = $e->search_action_circulation(
153 $search, {checkperm=>1, permorg=>$patron->home_ou})
157 $data = $e->search_action_circulation($search);
164 __PACKAGE__->register_method(
165 method => "title_from_transaction",
166 api_name => "open-ils.circ.circ_transaction.find_title",
167 NOTES => <<" NOTES");
168 Returns a mods object for the title that is linked to from the
169 copy from the hold that created the given transaction
172 sub title_from_transaction {
173 my( $self, $client, $login_session, $transactionid ) = @_;
175 my( $user, $circ, $title, $evt );
177 ( $user, $evt ) = $apputils->checkses( $login_session );
180 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
183 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
186 return $apputils->record_to_mvr($title);
190 __PACKAGE__->register_method(
191 method => "set_circ_lost",
192 api_name => "open-ils.circ.circulation.set_lost",
193 NOTES => <<" NOTES");
194 Params are login, barcode
195 login must have SET_CIRC_LOST perms
196 Sets a circulation to lost
199 __PACKAGE__->register_method(
200 method => "set_circ_lost",
201 api_name => "open-ils.circ.circulation.set_claims_returned",
202 NOTES => <<" NOTES");
203 Params are login, barcode
204 login must have SET_CIRC_MISSING perms
205 Sets a circulation to lost
209 my( $self, $client, $login, $args ) = @_;
210 my( $user, $circ, $copy, $evt );
212 my $barcode = $$args{barcode};
213 my $backdate = $$args{backdate};
215 ( $user, $evt ) = $U->checkses($login);
218 # Grab the related copy
219 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
222 my $isclaims = $self->api_name =~ /claims_returned/;
223 my $islost = $self->api_name =~ /lost/;
224 my $session = $U->start_db_session();
226 # grab the circulation
227 ( $circ ) = $U->fetch_open_circulation( $copy->id );
228 return 1 unless $circ;
231 $evt = _set_circ_lost($copy, $circ, $user, $session) if $islost;
236 $evt = _set_circ_claims_returned(
237 $user, $circ, $session, $backdate );
240 # $evt = $U->check_perms($user->id, $circ->circ_lib, 'SET_CIRC_CLAIMS_RETURNED');
241 # return $evt if $evt;
242 # $circ->stop_fines("CLAIMSRETURNED");
244 # $logger->activity("user ".$user->id." marking circ". $circ->id. " as claims returned");
246 # # allow the caller to backdate the circulation and void any fines
247 # # that occurred after the backdate
249 # OpenILS::Application::Circ::Circulate::_checkin_handle_backdate(
250 # $backdate, $circ, $user, $session );
254 # ($patron, $evt) = $U->fetch_user($circ->usr);
255 # return $evt if $evt;
256 # $patron->claims_returned_count(
257 # $patron->claims_returned_count + 1 );
259 # my $stat = $U->storagereq(
260 # 'open-ils.storage.direct.actor.user.update', $patron );
261 # return $U->DB_UPDATE_FAILED($patron) unless $stat;
265 $circ->stop_fines_time('now') unless $circ->stop_fines_time('now');
266 my $s = $session->request(
267 "open-ils.storage.direct.action.circulation.update", $circ )->gather(1);
269 return $U->DB_UPDATE_FAILED($circ) unless defined($s);
270 $U->commit_db_session($session);
276 my( $copy, $circ, $reqr, $session ) = @_;
278 my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_LOST');
281 $logger->activity("user ".$reqr->id." marking copy ".$copy->id.
282 " lost for circ ". $circ->id. " and checking for necessary charges");
284 my $newstat = $U->copy_status_from_name('lost');
285 if( $copy->status ne $newstat->id ) {
287 $copy->status($newstat);
291 session => $session);
294 # if the copy has a price defined and/or a processing fee, bill the patron
295 my $amount = $copy->price || 0;
296 my $owner = $U->fetch_copy_owner($copy->id);
297 $logger->info("circ fetching org settings for $owner to determine processing fee");
298 my $settings = $U->simplereq(
300 'open-ils.actor.org_unit.settings.retrieve', $owner );
301 my $f = $settings->{'circ.processing_fee'} || 0;
306 $logger->activity("The system is charging $amount ".
307 "for lost materials on circulation ".$circ->id);
309 my $bill = Fieldmapper::money::billing->new;
311 $bill->xact( $circ->id );
312 $bill->amount( $amount );
313 $bill->billing_type('Lost materials'); # - these strings should be configurable some day
314 $bill->note('SYSTEM GENERATED');
316 my $id = $session->request(
317 'open-ils.storage.direct.money.billing.create', $bill )->gather(1);
319 return $U->DB_UPDATE_FAILED($bill) unless defined $id;
322 $circ->stop_fines("LOST");
326 sub _set_circ_claims_returned {
327 my( $reqr, $circ, $session, $backdate ) = @_;
329 my $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'SET_CIRC_CLAIMS_RETURNED');
331 $circ->stop_fines("CLAIMSRETURNED");
333 $logger->activity("user ".$reqr->id.
334 " marking circ". $circ->id. " as claims returned");
336 # allow the caller to backdate the circulation and void any fines
337 # that occurred after the backdate
339 OpenILS::Application::Circ::Circulate::_checkin_handle_backdate(
340 $backdate, $circ, $reqr, $session );
348 __PACKAGE__->register_method (
349 method => 'set_circ_due_date',
350 api_name => 'open-ils.circ.circulation.due_date.update',
352 Updates the due_date on the given circ
354 @param circid The id of the circ to update
355 @param date The timestamp of the new due date
359 sub set_circ_due_date {
360 my( $s, $c, $authtoken, $circid, $date ) = @_;
361 my ($circ, $evt) = $U->fetch_circulation($circid);
365 ($reqr, $evt) = $U->checkses($authtoken);
368 $evt = $U->check_perms($reqr->id, $circ->circ_lib, 'CIRC_OVERRIDE_DUE_DATE');
371 $date = clense_ISO8601($date);
372 $logger->activity("user ".$reqr->id.
373 " updating due_date on circ $circid: $date");
375 $circ->due_date($date);
376 my $stat = $U->storagereq(
377 'open-ils.storage.direct.action.circulation.update', $circ);
378 return $U->DB_UPDATE_FAILED unless defined $stat;
383 __PACKAGE__->register_method(
384 method => "create_in_house_use",
385 api_name => 'open-ils.circ.in_house_use.create',
387 Creates an in-house use action.
388 @param $authtoken The login session key
389 @param params A hash of params including
390 'location' The org unit id where the in-house use occurs
391 'copyid' The copy in question
392 'count' The number of in-house uses to apply to this copy
393 @return An array of id's representing the id's of the newly created
394 in-house use objects or an event on an error
397 sub create_in_house_use {
398 my( $self, $client, $authtoken, $params ) = @_;
400 my( $staff, $evt, $copy );
401 my $org = $params->{location};
402 my $copyid = $params->{copyid};
403 my $count = $params->{count} || 1;
404 my $use_time = $params->{use_time} || 'now';
407 my $barcode = $params->{barcode};
408 ($copy, $evt) = $U->fetch_copy_by_barcode($barcode);
413 ($staff, $evt) = $U->checkses($authtoken);
416 ($copy, $evt) = $U->fetch_copy($copyid) unless $copy;
419 $evt = $U->check_perms($staff->id, $org, 'CREATE_IN_HOUSE_USE');
422 $logger->activity("User " . $staff->id .
423 " creating $count in-house use(s) for copy $copyid at location $org");
425 if( $use_time ne 'now' ) {
426 $use_time = clense_ISO8601($use_time);
427 $logger->debug("in_house_use setting use time to $use_time");
432 my $ihu = Fieldmapper::action::in_house_use->new;
435 $ihu->staff($staff->id);
436 $ihu->org_unit($org);
437 $ihu->use_time($use_time);
439 my $id = $U->simplereq(
441 'open-ils.storage.direct.action.in_house_use.create', $ihu );
443 return $U->DB_UPDATE_FAILED($ihu) unless $id;
452 __PACKAGE__->register_method(
453 method => "view_circs",
454 api_name => "open-ils.circ.copy_checkout_history.retrieve",
456 Retrieves the last X circs for a given copy
457 @param authtoken The login session key
458 @param copyid The copy to check
459 @param count How far to go back in the item history
460 @return An array of circ ids
464 my( $self, $client, $authtoken, $copyid, $count ) = @_;
466 my( $requestor, $evt ) = $U->checksesperm(
467 $authtoken, 'VIEW_COPY_CHECKOUT_HISTORY' );
470 return [] unless $count;
472 my $circs = $U->storagereq(
473 'open-ils.storage.direct.action.circulation.search_where.atomic',
475 target_copy => $copyid,
476 # opac_renewal => 'f',
477 # desk_renewal => 'f',
478 # phone_renewal => 'f',
482 order_by => "xact_start DESC"
486 # push(@users, $_->usr) for @$circs;
493 __PACKAGE__->register_method(
494 method => "circ_count",
495 api_name => "open-ils.circ.circulation.count",
497 Returns the number of times the item has circulated
498 @param copyid The copy to check
502 my( $self, $client, $copyid ) = @_;
503 my $circs = $U->storagereq(
504 'open-ils.storage.id_list.action.circulation.search.target_copy.atomic', $copyid );
505 return scalar @$circs;
510 __PACKAGE__->register_method(
511 method => 'fetch_notes',
512 api_name => 'open-ils.circ.copy_note.retrieve.all',
514 Returns an array of copy note objects.
515 @param args A named hash of parameters including:
516 authtoken : Required if viewing non-public notes
517 itemid : The id of the item whose notes we want to retrieve
518 pub : True if all the caller wants are public notes
519 @return An array of note objects
522 __PACKAGE__->register_method(
523 method => 'fetch_notes',
524 api_name => 'open-ils.circ.call_number_note.retrieve.all',
525 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
527 __PACKAGE__->register_method(
528 method => 'fetch_notes',
529 api_name => 'open-ils.circ.title_note.retrieve.all',
530 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
533 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
535 my( $self, $connection, $args ) = @_;
537 my $id = $$args{itemid};
538 my $authtoken = $$args{authtoken};
541 if( $self->api_name =~ /copy/ ) {
543 return $U->storagereq(
544 'open-ils.storage.direct.asset.copy_note.search_where.atomic',
545 { owning_copy => $id, pub => 't' } );
547 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
549 return $U->storagereq(
550 'open-ils.storage.direct.asset.copy_note.search.owning_copy.atomic', $id );
553 } elsif( $self->api_name =~ /call_number/ ) {
555 return $U->storagereq(
556 'open-ils.storage.direct.asset.call_number_note.search_where.atomic',
557 { call_number => $id, pub => 't' } );
559 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
561 return $U->storagereq(
562 'open-ils.storage.direct.asset.call_number_note.search.call_number.atomic', $id );
565 } elsif( $self->api_name =~ /title/ ) {
567 return $U->storagereq(
568 'open-ils.storage.direct.bilbio.record_note.search_where.atomic',
569 { record => $id, pub => 't' } );
571 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
573 return $U->storagereq(
574 'open-ils.storage.direct.biblio.record_note.search.record.atomic', $id );
581 __PACKAGE__->register_method(
582 method => 'has_notes',
583 api_name => 'open-ils.circ.copy.has_notes');
584 __PACKAGE__->register_method(
585 method => 'has_notes',
586 api_name => 'open-ils.circ.call_number.has_notes');
587 __PACKAGE__->register_method(
588 method => 'has_notes',
589 api_name => 'open-ils.circ.title.has_notes');
593 my( $self, $conn, $authtoken, $id ) = @_;
594 my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
595 return $editor->event unless $editor->checkauth;
597 my $n = $editor->search_asset_copy_note(
598 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
600 $n = $editor->search_asset_call_number_note(
601 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
603 $n = $editor->search_biblio_record_note(
604 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
609 __PACKAGE__->register_method(
610 method => 'create_copy_note',
611 api_name => 'open-ils.circ.copy_note.create',
613 Creates a new copy note
614 @param authtoken The login session key
615 @param note The note object to create
616 @return The id of the new note object
619 sub create_copy_note {
620 my( $self, $connection, $authtoken, $note ) = @_;
621 my( $cnowner, $requestor, $evt );
623 ($cnowner, $evt) = $U->fetch_copy_owner($note->owning_copy);
625 ($requestor, $evt) = $U->checkses($authtoken);
627 $evt = $U->check_perms($requestor->id, $cnowner, 'CREATE_COPY_NOTE');
630 $note->create_date('now');
631 $note->creator($requestor->id);
632 $note->pub( ($note->pub) ? 't' : 'f' );
634 my $id = $U->storagereq(
635 'open-ils.storage.direct.asset.copy_note.create', $note );
636 return $U->DB_UPDATE_FAILED($note) unless $id;
638 $logger->activity("User ".$requestor->id." created a new copy ".
639 "note [$id] for copy ".$note->owning_copy." with text ".$note->value);
644 __PACKAGE__->register_method(
645 method => 'delete_copy_note',
646 api_name => 'open-ils.circ.copy_note.delete',
648 Deletes an existing copy note
649 @param authtoken The login session key
650 @param noteid The id of the note to delete
651 @return 1 on success - Event otherwise.
654 sub delete_copy_note {
655 my( $self, $conn, $authtoken, $noteid ) = @_;
656 my( $requestor, $note, $owner, $evt );
658 ($requestor, $evt) = $U->checkses($authtoken);
661 ($note, $evt) = $U->fetch_copy_note($noteid);
664 if( $note->creator ne $requestor->id ) {
665 ($owner, $evt) = $U->fetch_copy_onwer($note->owning_copy);
667 $evt = $U->check_perms($requestor->id, $owner, 'DELETE_COPY_NOTE');
671 my $stat = $U->storagereq(
672 'open-ils.storage.direct.asset.copy_note.delete', $noteid );
673 return $U->DB_UPDATE_FAILED($noteid) unless $stat;
675 $logger->activity("User ".$requestor->id." deleted copy note $noteid");