1 package OpenILS::Application::Circ;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
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::CircNotify;
12 use OpenILS::Application::Circ::CreditCard;
13 use OpenILS::Application::Circ::Money;
14 use OpenILS::Application::Circ::NonCat;
15 use OpenILS::Application::Circ::CopyLocations;
16 use OpenILS::Application::Circ::CircCommon;
19 use DateTime::Format::ISO8601;
21 use OpenILS::Application::AppUtils;
23 use OpenSRF::Utils qw/:datetime/;
24 use OpenSRF::AppSession;
25 use OpenILS::Utils::ModsParser;
27 use OpenSRF::EX qw(:try);
28 use OpenSRF::Utils::Logger qw(:logger);
29 use OpenILS::Utils::Fieldmapper;
30 use OpenILS::Utils::CStoreEditor q/:funcs/;
31 use OpenILS::Const qw/:const/;
32 use OpenSRF::Utils::SettingsClient;
33 use OpenILS::Application::Cat::AssetCommon;
35 my $apputils = "OpenILS::Application::AppUtils";
38 my $holdcode = "OpenILS::Application::Circ::Holds";
40 # ------------------------------------------------------------------------
41 # Top level Circ package;
42 # ------------------------------------------------------------------------
46 OpenILS::Application::Circ::Circulate->initialize();
50 __PACKAGE__->register_method(
51 method => 'retrieve_circ',
53 api_name => 'open-ils.circ.retrieve',
55 Retrieve a circ object by id
56 @param authtoken Login session key
57 @pararm circid The id of the circ object
61 my( $s, $c, $a, $i ) = @_;
62 my $e = new_editor(authtoken => $a);
63 return $e->event unless $e->checkauth;
64 my $circ = $e->retrieve_action_circulation($i) or return $e->event;
65 if( $e->requestor->id ne $circ->usr ) {
66 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
72 __PACKAGE__->register_method(
73 method => 'fetch_circ_mods',
74 api_name => 'open-ils.circ.circ_modifier.retrieve.all');
76 my($self, $conn, $args) = @_;
77 my $mods = new_editor()->retrieve_all_config_circ_modifier;
78 return [ map {$_->code} @$mods ] unless $$args{full};
82 __PACKAGE__->register_method(
83 method => 'ranged_billing_types',
84 api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
86 sub ranged_billing_types {
87 my($self, $conn, $auth, $org_id, $depth) = @_;
88 my $e = new_editor(authtoken => $auth);
89 return $e->event unless $e->checkauth;
90 return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
91 return $e->search_config_billing_type(
92 {owner => $U->get_org_full_path($org_id, $depth)});
97 # ------------------------------------------------------------------------
98 # Returns an array of {circ, record} hashes checked out by the user.
99 # ------------------------------------------------------------------------
100 __PACKAGE__->register_method(
101 method => "checkouts_by_user",
102 api_name => "open-ils.circ.actor.user.checked_out",
104 NOTES => <<" NOTES");
105 Returns a list of open circulations as a pile of objects. Each object
106 contains the relevant copy, circ, and record
109 sub checkouts_by_user {
110 my($self, $client, $auth, $user_id) = @_;
112 my $e = new_editor(authtoken=>$auth);
113 return $e->event unless $e->checkauth;
115 my $circ_ids = $e->search_action_circulation(
117 checkin_time => undef,
119 {stop_fines => undef},
120 {stop_fines => ['MAXFINES','LONGOVERDUE']}
126 for my $id (@$circ_ids) {
127 my $circ = $e->retrieve_action_circulation([
131 circ => ['target_copy'],
132 acp => ['call_number'],
138 # un-flesh for consistency
139 my $c = $circ->target_copy;
140 $circ->target_copy($c->id);
142 my $cn = $c->call_number;
143 $c->call_number($cn->id);
151 record => $U->record_to_mvr($t)
161 __PACKAGE__->register_method(
162 method => "checkouts_by_user_slim",
163 api_name => "open-ils.circ.actor.user.checked_out.slim",
164 NOTES => <<" NOTES");
165 Returns a list of open circulation objects
169 sub checkouts_by_user_slim {
170 my( $self, $client, $user_session, $user_id ) = @_;
172 my( $requestor, $target, $copy, $record, $evt );
174 ( $requestor, $target, $evt ) =
175 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
178 $logger->debug( 'User ' . $requestor->id .
179 " retrieving checked out items for user " . $target->id );
181 # XXX Make the call correct..
182 return $apputils->simplereq(
184 "open-ils.cstore.direct.action.open_circulation.search.atomic",
185 { usr => $target->id, checkin_time => undef } );
186 # { usr => $target->id } );
190 __PACKAGE__->register_method(
191 method => "checkouts_by_user_opac",
192 api_name => "open-ils.circ.actor.user.checked_out.opac",);
195 sub checkouts_by_user_opac {
196 my( $self, $client, $auth, $user_id ) = @_;
198 my $e = new_editor( authtoken => $auth );
199 return $e->event unless $e->checkauth;
200 $user_id ||= $e->requestor->id;
201 return $e->event unless
202 my $patron = $e->retrieve_actor_user($user_id);
205 my $search = {usr => $user_id, stop_fines => undef};
207 if( $user_id ne $e->requestor->id ) {
208 $data = $e->search_action_circulation(
209 $search, {checkperm=>1, permorg=>$patron->home_ou})
213 $data = $e->search_action_circulation($search);
220 __PACKAGE__->register_method(
221 method => "title_from_transaction",
222 api_name => "open-ils.circ.circ_transaction.find_title",
223 NOTES => <<" NOTES");
224 Returns a mods object for the title that is linked to from the
225 copy from the hold that created the given transaction
228 sub title_from_transaction {
229 my( $self, $client, $login_session, $transactionid ) = @_;
231 my( $user, $circ, $title, $evt );
233 ( $user, $evt ) = $apputils->checkses( $login_session );
236 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
239 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
242 return $apputils->record_to_mvr($title);
245 __PACKAGE__->register_method(
246 method => "staff_age_to_lost",
247 api_name => "open-ils.circ.circulation.age_to_lost",
250 This fires a circ.staff_age_to_lost Action-Trigger event against all
251 overdue circulations in scope of the specified context library and
252 user profile, which effectively marks the associated items as Lost.
253 This is likely to be done at the end of a semester in an academic
256 @param args : circ_lib, user_profile
260 sub staff_age_to_lost {
261 my( $self, $conn, $auth, $args ) = @_;
262 my $e = new_editor(authtoken=>$auth);
263 return $e->event unless $e->checkauth;
264 return $e->event unless $e->allowed('SET_CIRC_LOST', $args->{'circ_lib'});
266 my $orgs = $U->get_org_descendants($args->{'circ_lib'});
267 my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
269 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
271 my $method = 'open-ils.trigger.passive.event.autocreate.batch';
272 my $hook = 'circ.staff_age_to_lost';
273 my $context_org = 'circ_lib';
274 my $opt_granularity = undef;
276 "checkin_time" => undef,
277 "due_date" => { "<" => "now" },
279 { "stop_fines" => ["MAXFINES", "LONGOVERDUE"] }, # FIXME: CLAIMSRETURNED also?
280 { "stop_fines" => undef }
284 "select" => {"au" => ["id"]},
287 "profile" => $profiles,
288 "id" => { "=" => {"+circ" => "usr"} }
292 "select" => {"aou" => ["id"]},
296 {"id" => { "=" => {"+circ" => "circ_lib"} }},
303 my $req_timeout = 10800;
304 my $chunk_size = 100;
307 my $req = $ses->request($method, $hook, $context_org, $filter, $opt_granularity);
308 my @event_ids; my @chunked_ids;
309 while (my $resp = $req->recv(timeout => $req_timeout)) {
310 push(@event_ids, $resp->content);
311 push(@chunked_ids, $resp->content);
312 if (scalar(@chunked_ids) > $chunk_size) {
313 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
317 if (scalar(@chunked_ids) > 0) {
318 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
322 $logger->info("staff_age_to_lost: created ".scalar(@event_ids)." events for circ.staff_age_to_lost");
323 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>scalar(@event_ids)});
324 } elsif($req->complete) {
325 $logger->info("staff_age_to_lost: no events to create for circ.staff_age_to_lost");
326 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>0});
328 $logger->warn("staff_age_to_lost: timeout occurred during event creation for circ.staff_age_to_lost");
329 $conn->respond_complete({'total_progress'=>$progress-1,'error'=>'timeout'});
336 __PACKAGE__->register_method(
337 method => "new_set_circ_lost",
338 api_name => "open-ils.circ.circulation.set_lost",
340 Sets the copy and related open circulation to lost
342 @param args : barcode
347 # ---------------------------------------------------------------------
348 # Sets a circulation to lost. updates copy status to lost
349 # applies copy and/or prcoessing fees depending on org settings
350 # ---------------------------------------------------------------------
351 sub new_set_circ_lost {
352 my( $self, $conn, $auth, $args ) = @_;
354 my $e = new_editor(authtoken=>$auth, xact=>1);
355 return $e->die_event unless $e->checkauth;
357 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
358 or return $e->die_event;
360 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
368 __PACKAGE__->register_method(
369 method => "set_circ_claims_returned",
370 api_name => "open-ils.circ.circulation.set_claims_returned",
372 desc => q/Sets the circ for a given item as claims returned
373 If a backdate is provided, overdue fines will be voided
374 back to the backdate/,
376 {desc => 'Authentication token', type => 'string'},
377 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
379 return => {desc => q/1 on success, failure event on error, and
380 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
381 configured claims return maximum/}
385 __PACKAGE__->register_method(
386 method => "set_circ_claims_returned",
387 api_name => "open-ils.circ.circulation.set_claims_returned.override",
389 desc => q/This adds support for overrideing the configured max
390 claims returned amount.
391 @see open-ils.circ.circulation.set_claims_returned./,
395 sub set_circ_claims_returned {
396 my( $self, $conn, $auth, $args, $oargs ) = @_;
398 my $e = new_editor(authtoken=>$auth, xact=>1);
399 return $e->die_event unless $e->checkauth;
401 $oargs = { all => 1 } unless defined $oargs;
403 my $barcode = $$args{barcode};
404 my $backdate = $$args{backdate};
406 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
407 or return $e->die_event;
409 my $circ = $e->search_action_circulation(
410 {checkin_time => undef, target_copy => $copy->id})->[0]
411 or return $e->die_event;
413 $backdate = $circ->due_date if $$args{use_due_date};
415 $logger->info("marking circ for item $barcode as claims returned".
416 (($backdate) ? " with backdate $backdate" : ''));
418 my $patron = $e->retrieve_actor_user($circ->usr);
419 my $max_count = $U->ou_ancestor_setting_value(
420 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
422 # If the patron has too instances of many claims returned,
423 # require an override to continue. A configured max of
424 # 0 means all attempts require an override
425 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
427 if($self->api_name =~ /override/ && ($oargs->{all} || grep { $_ eq 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT' } @{$oargs->{events}})) {
429 # see if we're allowed to override
430 return $e->die_event unless
431 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
435 # exit early and return the max claims return event
437 return OpenILS::Event->new(
438 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
440 patron_count => $patron->claims_returned_count,
441 max_count => $max_count
447 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
448 or return $e->die_event;
450 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
451 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
454 $backdate = cleanse_ISO8601($backdate);
456 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
457 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
458 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
460 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
461 $backdate = cleanse_ISO8601($backdate);
463 # make it look like the circ stopped at the cliams returned time
464 $circ->stop_fines_time($backdate);
465 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate, note => 'System: OVERDUE REVERSED FOR CLAIMS-RETURNED', force_zero => 1});
469 $e->update_action_circulation($circ) or return $e->die_event;
471 # see if there is a configured post-claims-return copy status
472 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
473 $copy->status($stat);
474 $copy->edit_date('now');
475 $copy->editor($e->requestor->id);
476 $e->update_asset_copy($copy) or return $e->die_event;
479 # Check if the copy circ lib wants lost fees voided on claims
481 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_on_claimsreturned', $e))) {
482 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
493 # Check if the copy circ lib wants lost processing fees voided on
495 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_proc_fee_on_claimsreturned', $e))) {
496 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
507 # Check if the copy circ lib wants longoverdue fees voided on claims
509 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_on_claimsreturned', $e))) {
510 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
521 # Check if the copy circ lib wants longoverdue processing fees voided on
523 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_proc_fee_on_claimsreturned', $e))) {
524 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
540 __PACKAGE__->register_method(
541 method => "post_checkin_backdate_circ",
542 api_name => "open-ils.circ.post_checkin_backdate",
544 desc => q/Back-date an already checked in circulation/,
546 {desc => 'Authentication token', type => 'string'},
547 {desc => 'Circ ID', type => 'number'},
548 {desc => 'ISO8601 backdate', type => 'string'},
550 return => {desc => q/1 on success, failure event on error/}
554 __PACKAGE__->register_method(
555 method => "post_checkin_backdate_circ",
556 api_name => "open-ils.circ.post_checkin_backdate.batch",
559 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
561 {desc => 'Authentication token', type => 'string'},
562 {desc => 'List of Circ ID', type => 'array'},
563 {desc => 'ISO8601 backdate', type => 'string'},
565 return => {desc => q/Set of: 1 on success, failure event on error/}
570 sub post_checkin_backdate_circ {
571 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
572 my $e = new_editor(authtoken=>$auth);
573 return $e->die_event unless $e->checkauth;
574 if($self->api_name =~ /batch/) {
575 foreach my $c (@$circ_id) {
576 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
579 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
587 sub post_checkin_backdate_circ_impl {
588 my($e, $circ_id, $backdate) = @_;
592 my $circ = $e->retrieve_action_circulation($circ_id)
593 or return $e->die_event;
595 # anyone with checkin perms can backdate (more restrictive?)
596 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
598 # don't allow back-dating an open circulation
599 return OpenILS::Event->new('BAD_PARAMS') unless
600 $backdate and $circ->checkin_time;
602 # update the checkin and stop_fines times to reflect the new backdate
603 $circ->stop_fines_time(cleanse_ISO8601($backdate));
604 $circ->checkin_time(cleanse_ISO8601($backdate));
605 $e->update_action_circulation($circ) or return $e->die_event;
607 # now void the overdues "erased" by the back-dating
608 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate});
611 # If the circ was closed before and the balance owned !=0, re-open the transaction
612 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
621 __PACKAGE__->register_method (
622 method => 'set_circ_due_date',
623 api_name => 'open-ils.circ.circulation.due_date.update',
625 Updates the due_date on the given circ
627 @param circid The id of the circ to update
628 @param date The timestamp of the new due date
632 sub set_circ_due_date {
633 my( $self, $conn, $auth, $circ_id, $date ) = @_;
635 my $e = new_editor(xact=>1, authtoken=>$auth);
636 return $e->die_event unless $e->checkauth;
637 my $circ = $e->retrieve_action_circulation($circ_id)
638 or return $e->die_event;
640 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
641 $date = cleanse_ISO8601($date);
643 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
644 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
645 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
646 $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
649 $circ->due_date($date);
650 $e->update_action_circulation($circ) or return $e->die_event;
657 __PACKAGE__->register_method(
658 method => "create_in_house_use",
659 api_name => 'open-ils.circ.in_house_use.create',
661 Creates an in-house use action.
662 @param $authtoken The login session key
663 @param params A hash of params including
664 'location' The org unit id where the in-house use occurs
665 'copyid' The copy in question
666 'count' The number of in-house uses to apply to this copy
667 @return An array of id's representing the id's of the newly created
668 in-house use objects or an event on an error
671 __PACKAGE__->register_method(
672 method => "create_in_house_use",
673 api_name => 'open-ils.circ.non_cat_in_house_use.create',
677 sub create_in_house_use {
678 my( $self, $client, $auth, $params ) = @_;
681 my $org = $params->{location};
682 my $copyid = $params->{copyid};
683 my $count = $params->{count} || 1;
684 my $nc_type = $params->{non_cat_type};
685 my $use_time = $params->{use_time} || 'now';
687 my $e = new_editor(xact=>1,authtoken=>$auth);
688 return $e->event unless $e->checkauth;
689 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
691 my $non_cat = 1 if $self->api_name =~ /non_cat/;
695 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
697 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
703 if( $use_time ne 'now' ) {
704 $use_time = cleanse_ISO8601($use_time);
705 $logger->debug("in_house_use setting use time to $use_time");
716 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
717 $ihu->item_type($nc_type);
718 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
719 $cmeth = "create_action_non_cat_in_house_use";
722 $ihu = Fieldmapper::action::in_house_use->new;
724 $method = 'open-ils.storage.direct.action.in_house_use.create';
725 $cmeth = "create_action_in_house_use";
728 $ihu->staff($e->requestor->id);
729 $ihu->org_unit($org);
730 $ihu->use_time($use_time);
732 $ihu = $e->$cmeth($ihu) or return $e->event;
733 push( @ids, $ihu->id );
744 __PACKAGE__->register_method(
745 method => "view_circs",
746 api_name => "open-ils.circ.copy_checkout_history.retrieve",
748 Retrieves the last X circs for a given copy
749 @param authtoken The login session key
750 @param copyid The copy to check
751 @param count How far to go back in the item history
752 @return An array of circ ids
755 # ----------------------------------------------------------------------
756 # Returns $count most recent circs. If count exceeds the configured
757 # max, use the configured max instead
758 # ----------------------------------------------------------------------
760 my( $self, $client, $authtoken, $copyid, $count ) = @_;
762 my $e = new_editor(authtoken => $authtoken);
763 return $e->event unless $e->checkauth;
765 my $copy = $e->retrieve_asset_copy([
768 flesh_fields => {acp => ['call_number']}
770 ]) or return $e->event;
772 return $e->event unless $e->allowed(
773 'VIEW_COPY_CHECKOUT_HISTORY',
774 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
775 $copy->circ_lib : $copy->call_number->owning_lib);
777 my $max_history = $U->ou_ancestor_setting_value(
778 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
780 if(defined $max_history) {
781 $count = $max_history unless defined $count and $count < $max_history;
783 $count = 4 unless defined $count;
786 return $e->search_action_circulation([
787 {target_copy => $copyid},
788 {limit => $count, order_by => { circ => "xact_start DESC" }}
793 __PACKAGE__->register_method(
794 method => "circ_count",
795 api_name => "open-ils.circ.circulation.count",
797 Returns the number of times the item has circulated
798 @param copyid The copy to check
802 my( $self, $client, $copyid ) = @_;
804 my $count = new_editor()->json_query({
813 where => {'+circbyyr' => {copy => $copyid}}
825 __PACKAGE__->register_method(
826 method => 'fetch_notes',
828 api_name => 'open-ils.circ.copy_note.retrieve.all',
830 Returns an array of copy note objects.
831 @param args A named hash of parameters including:
832 authtoken : Required if viewing non-public notes
833 itemid : The id of the item whose notes we want to retrieve
834 pub : True if all the caller wants are public notes
835 @return An array of note objects
838 __PACKAGE__->register_method(
839 method => 'fetch_notes',
840 api_name => 'open-ils.circ.call_number_note.retrieve.all',
841 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
843 __PACKAGE__->register_method(
844 method => 'fetch_notes',
845 api_name => 'open-ils.circ.title_note.retrieve.all',
846 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
849 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
851 my( $self, $connection, $args ) = @_;
853 my $id = $$args{itemid};
854 my $authtoken = $$args{authtoken};
857 if( $self->api_name =~ /copy/ ) {
859 return $U->cstorereq(
860 'open-ils.cstore.direct.asset.copy_note.search.atomic',
861 { owning_copy => $id, pub => 't' } );
863 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
865 return $U->cstorereq(
866 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
869 } elsif( $self->api_name =~ /call_number/ ) {
871 return $U->cstorereq(
872 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
873 { call_number => $id, pub => 't' } );
875 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
877 return $U->cstorereq(
878 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
881 } elsif( $self->api_name =~ /title/ ) {
883 return $U->cstorereq(
884 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
885 { record => $id, pub => 't' } );
887 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
889 return $U->cstorereq(
890 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
897 __PACKAGE__->register_method(
898 method => 'has_notes',
899 api_name => 'open-ils.circ.copy.has_notes');
900 __PACKAGE__->register_method(
901 method => 'has_notes',
902 api_name => 'open-ils.circ.call_number.has_notes');
903 __PACKAGE__->register_method(
904 method => 'has_notes',
905 api_name => 'open-ils.circ.title.has_notes');
909 my( $self, $conn, $authtoken, $id ) = @_;
910 my $editor = new_editor(authtoken => $authtoken);
911 return $editor->event unless $editor->checkauth;
913 my $n = $editor->search_asset_copy_note(
914 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
916 $n = $editor->search_asset_call_number_note(
917 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
919 $n = $editor->search_biblio_record_note(
920 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
927 __PACKAGE__->register_method(
928 method => 'create_copy_note',
929 api_name => 'open-ils.circ.copy_note.create',
931 Creates a new copy note
932 @param authtoken The login session key
933 @param note The note object to create
934 @return The id of the new note object
937 sub create_copy_note {
938 my( $self, $connection, $authtoken, $note ) = @_;
940 my $e = new_editor(xact=>1, authtoken=>$authtoken);
941 return $e->event unless $e->checkauth;
942 my $copy = $e->retrieve_asset_copy(
946 flesh_fields => { 'acp' => ['call_number'] }
951 return $e->event unless
952 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
954 $note->create_date('now');
955 $note->creator($e->requestor->id);
956 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
959 $e->create_asset_copy_note($note) or return $e->event;
965 __PACKAGE__->register_method(
966 method => 'delete_copy_note',
967 api_name => 'open-ils.circ.copy_note.delete',
969 Deletes an existing copy note
970 @param authtoken The login session key
971 @param noteid The id of the note to delete
972 @return 1 on success - Event otherwise.
974 sub delete_copy_note {
975 my( $self, $conn, $authtoken, $noteid ) = @_;
977 my $e = new_editor(xact=>1, authtoken=>$authtoken);
978 return $e->die_event unless $e->checkauth;
980 my $note = $e->retrieve_asset_copy_note([
984 'acpn' => [ 'owning_copy' ],
985 'acp' => [ 'call_number' ],
988 ]) or return $e->die_event;
990 if( $note->creator ne $e->requestor->id ) {
991 return $e->die_event unless
992 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
995 $e->delete_asset_copy_note($note) or return $e->die_event;
1001 __PACKAGE__->register_method(
1002 method => 'age_hold_rules',
1003 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
1006 sub age_hold_rules {
1007 my( $self, $conn ) = @_;
1008 return new_editor()->retrieve_all_config_rules_age_hold_protect();
1013 __PACKAGE__->register_method(
1014 method => 'copy_details_barcode',
1016 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
1017 sub copy_details_barcode {
1018 my( $self, $conn, $auth, $barcode ) = @_;
1019 my $e = new_editor();
1020 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
1021 return $e->event unless $cid;
1022 return copy_details( $self, $conn, $auth, $cid );
1026 __PACKAGE__->register_method(
1027 method => 'copy_details',
1028 api_name => 'open-ils.circ.copy_details.retrieve');
1031 my( $self, $conn, $auth, $copy_id ) = @_;
1032 my $e = new_editor(authtoken=>$auth);
1033 return $e->event unless $e->checkauth;
1035 my $flesh = { flesh => 1 };
1037 my $copy = $e->retrieve_asset_copy(
1043 acp => ['call_number','parts','peer_record_maps','floating'],
1044 acn => ['record','prefix','suffix','label_class']
1047 ]) or return $e->event;
1050 # De-flesh the copy for backwards compatibility
1052 my $vol = $copy->call_number;
1054 $copy->call_number($vol->id);
1055 my $record = $vol->record;
1057 $vol->record($record->id);
1058 $mvr = $U->record_to_mvr($record);
1063 my $hold = $e->search_action_hold_request(
1065 current_copy => $copy_id,
1066 capture_time => { "!=" => undef },
1067 fulfillment_time => undef,
1068 cancel_time => undef,
1072 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
1074 my $transit = $e->search_action_transit_copy(
1075 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
1077 # find the latest circ, open or closed
1078 my $circ = $e->search_action_circulation(
1080 { target_copy => $copy_id },
1086 'checkin_workstation',
1089 'recurring_fine_rule'
1092 order_by => { circ => 'xact_start desc' },
1102 transit => $transit,
1112 __PACKAGE__->register_method(
1113 method => 'mark_item',
1114 api_name => 'open-ils.circ.mark_item_damaged',
1116 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
1117 @param authtoken The login session key
1118 @param copy_id The ID of the copy to mark as damaged
1119 @return 1 on success - Event otherwise.
1122 __PACKAGE__->register_method(
1123 method => 'mark_item',
1124 api_name => 'open-ils.circ.mark_item_missing',
1126 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
1127 @param authtoken The login session key
1128 @param copy_id The ID of the copy to mark as missing
1129 @return 1 on success - Event otherwise.
1132 __PACKAGE__->register_method(
1133 method => 'mark_item',
1134 api_name => 'open-ils.circ.mark_item_bindery',
1136 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
1137 @param authtoken The login session key
1138 @param copy_id The ID of the copy to mark as bindery
1139 @return 1 on success - Event otherwise.
1142 __PACKAGE__->register_method(
1143 method => 'mark_item',
1144 api_name => 'open-ils.circ.mark_item_on_order',
1146 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
1147 @param authtoken The login session key
1148 @param copy_id The ID of the copy to mark as on order
1149 @return 1 on success - Event otherwise.
1152 __PACKAGE__->register_method(
1153 method => 'mark_item',
1154 api_name => 'open-ils.circ.mark_item_ill',
1156 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1157 @param authtoken The login session key
1158 @param copy_id The ID of the copy to mark as inter-library loan
1159 @return 1 on success - Event otherwise.
1162 __PACKAGE__->register_method(
1163 method => 'mark_item',
1164 api_name => 'open-ils.circ.mark_item_cataloging',
1166 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1167 @param authtoken The login session key
1168 @param copy_id The ID of the copy to mark as cataloging
1169 @return 1 on success - Event otherwise.
1172 __PACKAGE__->register_method(
1173 method => 'mark_item',
1174 api_name => 'open-ils.circ.mark_item_reserves',
1176 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1177 @param authtoken The login session key
1178 @param copy_id The ID of the copy to mark as reserves
1179 @return 1 on success - Event otherwise.
1182 __PACKAGE__->register_method(
1183 method => 'mark_item',
1184 api_name => 'open-ils.circ.mark_item_discard',
1186 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1187 @param authtoken The login session key
1188 @param copy_id The ID of the copy to mark as discard
1189 @return 1 on success - Event otherwise.
1194 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1195 my $e = new_editor(authtoken=>$auth, xact =>1);
1196 return $e->die_event unless $e->checkauth;
1199 my $copy = $e->retrieve_asset_copy([
1201 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1202 or return $e->die_event;
1205 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1206 $copy->circ_lib : $copy->call_number->owning_lib;
1208 my $perm = 'MARK_ITEM_MISSING';
1209 my $stat = OILS_COPY_STATUS_MISSING;
1211 if( $self->api_name =~ /damaged/ ) {
1212 $perm = 'MARK_ITEM_DAMAGED';
1213 $stat = OILS_COPY_STATUS_DAMAGED;
1214 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1215 return $evt if $evt;
1217 } elsif ( $self->api_name =~ /bindery/ ) {
1218 $perm = 'MARK_ITEM_BINDERY';
1219 $stat = OILS_COPY_STATUS_BINDERY;
1220 } elsif ( $self->api_name =~ /on_order/ ) {
1221 $perm = 'MARK_ITEM_ON_ORDER';
1222 $stat = OILS_COPY_STATUS_ON_ORDER;
1223 } elsif ( $self->api_name =~ /ill/ ) {
1224 $perm = 'MARK_ITEM_ILL';
1225 $stat = OILS_COPY_STATUS_ILL;
1226 } elsif ( $self->api_name =~ /cataloging/ ) {
1227 $perm = 'MARK_ITEM_CATALOGING';
1228 $stat = OILS_COPY_STATUS_CATALOGING;
1229 } elsif ( $self->api_name =~ /reserves/ ) {
1230 $perm = 'MARK_ITEM_RESERVES';
1231 $stat = OILS_COPY_STATUS_RESERVES;
1232 } elsif ( $self->api_name =~ /discard/ ) {
1233 $perm = 'MARK_ITEM_DISCARD';
1234 $stat = OILS_COPY_STATUS_DISCARD;
1237 # caller may proceed if either perm is allowed
1238 return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
1240 $copy->status($stat);
1241 $copy->edit_date('now');
1242 $copy->editor($e->requestor->id);
1244 $e->update_asset_copy($copy) or return $e->die_event;
1246 my $holds = $e->search_action_hold_request(
1248 current_copy => $copy->id,
1249 fulfillment_time => undef,
1250 cancel_time => undef,
1256 if( $self->api_name =~ /damaged/ ) {
1257 # now that we've committed the changes, create related A/T events
1258 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1259 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1262 $logger->debug("resetting holds that target the marked copy");
1263 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1268 sub handle_mark_damaged {
1269 my($e, $copy, $owning_lib, $args) = @_;
1271 my $apply = $args->{apply_fines} || '';
1272 return undef if $apply eq 'noapply';
1274 my $new_amount = $args->{override_amount};
1275 my $new_btype = $args->{override_btype};
1276 my $new_note = $args->{override_note};
1278 # grab the last circulation
1279 my $circ = $e->search_action_circulation([
1280 { target_copy => $copy->id},
1282 order_by => {circ => "xact_start DESC"},
1284 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1288 return undef unless $circ;
1290 my $charge_price = $U->ou_ancestor_setting_value(
1291 $owning_lib, 'circ.charge_on_damaged', $e);
1293 my $proc_fee = $U->ou_ancestor_setting_value(
1294 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1296 my $void_overdue = $U->ou_ancestor_setting_value(
1297 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1299 return undef unless $charge_price or $proc_fee;
1301 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1302 my $total = $copy_price + $proc_fee;
1306 if($new_amount and $new_btype) {
1308 # Allow staff to override the amount to charge for a damaged item
1309 # Consider the case where the item is only partially damaged
1310 # This value is meant to take the place of the item price and
1311 # optional processing fee.
1313 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1314 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1315 return $evt if $evt;
1319 if($charge_price and $copy_price) {
1320 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1321 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1322 return $evt if $evt;
1326 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1327 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1328 return $evt if $evt;
1332 # the assumption is that you would not void the overdues unless you
1333 # were also charging for the item and/or applying a processing fee
1335 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {note => 'System: OVERDUE REVERSED FOR DAMAGE CHARGE'});
1336 return $evt if $evt;
1339 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1340 return $evt if $evt;
1342 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1343 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1345 my $evt2 = OpenILS::Utils::Penalty->calculate_penalties($e, $circ->usr->id, $e->requestor->ws_ou);
1346 return $evt2 if $evt2;
1351 return OpenILS::Event->new('DAMAGE_CHARGE',
1362 # ----------------------------------------------------------------------
1363 __PACKAGE__->register_method(
1364 method => 'mark_item_missing_pieces',
1365 api_name => 'open-ils.circ.mark_item_missing_pieces',
1367 Changes the status of a copy to "damaged" or to a custom status based on the
1368 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1370 @param authtoken The login session key
1371 @param copy_id The ID of the copy to mark as damaged
1372 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1376 sub mark_item_missing_pieces {
1377 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1378 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1379 ### FIXME: Even better, we're going to use two transactions, the first to affect pertinent holds before checkout can
1381 my $e2 = new_editor(authtoken=>$auth, xact =>1);
1382 return $e2->die_event unless $e2->checkauth;
1385 my $copy = $e2->retrieve_asset_copy([
1387 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1388 or return $e2->die_event;
1391 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1392 $copy->circ_lib : $copy->call_number->owning_lib;
1394 return $e2->die_event unless $e2->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1396 #### grab the last circulation
1397 my $circ = $e2->search_action_circulation([
1398 { target_copy => $copy->id},
1400 order_by => {circ => "xact_start DESC"}
1405 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1407 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1410 my $holds = $e2->search_action_hold_request(
1412 current_copy => $copy->id,
1413 fulfillment_time => undef,
1414 cancel_time => undef,
1418 $logger->debug("resetting holds that target the marked copy");
1419 OpenILS::Application::Circ::Holds->_reset_hold($e2->requestor, $_) for @$holds;
1422 if (! $e2->commit) {
1423 return $e2->die_event;
1426 my $e = new_editor(authtoken=>$auth, xact =>1);
1427 return $e->die_event unless $e->checkauth;
1429 if (! $circ->checkin_time) { # if circ active, attempt renew
1430 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1431 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1432 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1433 $circ = $res->[0]->{payload}{'circ'};
1434 $circ->target_copy( $copy->id );
1435 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1437 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1442 'copy_id'=>$circ->target_copy,
1443 'patron_id'=>$circ->usr,
1444 'skip_deposit_fee'=>1,
1445 'skip_rental_fee'=>1
1448 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1450 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1451 $e, $copy, $e->requestor, 1 );
1453 if ($hold) { # needed for hold? then due now
1455 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1456 my $due_date = DateTime->now(time_zone => 'local');
1457 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1459 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1463 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params,{ all => 1 });
1464 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1465 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1466 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1467 $circ = $res->[0]->{payload}{'circ'};
1469 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1475 ### Update the item status
1477 my $custom_stat = $U->ou_ancestor_setting_value(
1478 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1479 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1481 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1482 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1484 $copy->status($stat);
1485 $copy->edit_date('now');
1486 $copy->editor($e->requestor->id);
1488 $e->update_asset_copy($copy) or return $e->die_event;
1492 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1493 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1495 return OpenILS::Event->new('SUCCESS',
1499 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1500 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1505 return $e->die_event;
1513 # ----------------------------------------------------------------------
1514 __PACKAGE__->register_method(
1515 method => 'magic_fetch',
1516 api_name => 'open-ils.agent.fetch'
1519 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1522 my( $self, $conn, $auth, $args ) = @_;
1523 my $e = new_editor( authtoken => $auth );
1524 return $e->event unless $e->checkauth;
1526 my $hint = $$args{hint};
1527 my $id = $$args{id};
1529 # Is the call allowed to fetch this type of object?
1530 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1532 # Find the class the implements the given hint
1533 my ($class) = grep {
1534 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1536 $class =~ s/Fieldmapper:://og;
1537 $class =~ s/::/_/og;
1538 my $method = "retrieve_$class";
1540 my $obj = $e->$method($id) or return $e->event;
1543 # ----------------------------------------------------------------------
1546 __PACKAGE__->register_method(
1547 method => "fleshed_circ_retrieve",
1549 api_name => "open-ils.circ.fleshed.retrieve",);
1551 sub fleshed_circ_retrieve {
1552 my( $self, $client, $id ) = @_;
1553 my $e = new_editor();
1554 my $circ = $e->retrieve_action_circulation(
1560 circ => [ qw/ target_copy / ],
1561 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number parts / ],
1562 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1563 acn => [ qw/ record / ],
1567 ) or return $e->event;
1569 my $copy = $circ->target_copy;
1570 my $vol = $copy->call_number;
1571 my $rec = $circ->target_copy->call_number->record;
1573 $vol->record($rec->id);
1574 $copy->call_number($vol->id);
1575 $circ->target_copy($copy->id);
1579 if( $rec->id == OILS_PRECAT_RECORD ) {
1583 $mvr = $U->record_to_mvr($rec);
1584 $rec->marc(''); # drop the bulky marc data
1598 __PACKAGE__->register_method(
1599 method => "test_batch_circ_events",
1600 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1603 # method for testing the behavior of a given event definition
1604 sub test_batch_circ_events {
1605 my($self, $conn, $auth, $event_def, $barcode) = @_;
1607 my $e = new_editor(authtoken => $auth);
1608 return $e->event unless $e->checkauth;
1609 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1611 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1612 or return $e->event;
1614 my $circ = $e->search_action_circulation(
1615 {target_copy => $copy->id, checkin_time => undef})->[0]
1616 or return $e->event;
1618 return undef unless $circ;
1620 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1624 __PACKAGE__->register_method(
1625 method => "fire_circ_events",
1626 api_name => "open-ils.circ.fire_circ_trigger_events",
1628 General event def runner for circ objects. If no event def ID
1629 is provided, the hook will be used to find the best event_def
1630 match based on the context org unit
1634 __PACKAGE__->register_method(
1635 method => "fire_circ_events",
1636 api_name => "open-ils.circ.fire_hold_trigger_events",
1638 General event def runner for hold objects. If no event def ID
1639 is provided, the hook will be used to find the best event_def
1640 match based on the context org unit
1644 __PACKAGE__->register_method(
1645 method => "fire_circ_events",
1646 api_name => "open-ils.circ.fire_user_trigger_events",
1648 General event def runner for user objects. If no event def ID
1649 is provided, the hook will be used to find the best event_def
1650 match based on the context org unit
1655 sub fire_circ_events {
1656 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1658 my $e = new_editor(authtoken => $auth, xact => 1);
1659 return $e->event unless $e->checkauth;
1663 if($self->api_name =~ /hold/) {
1664 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1665 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1666 } elsif($self->api_name =~ /user/) {
1667 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1668 $targets = $e->batch_retrieve_actor_user($target_ids);
1670 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1671 $targets = $e->batch_retrieve_action_circulation($target_ids);
1673 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1674 # simply making this method authoritative because of weirdness
1675 # with transaction handling in A/T code that causes rollback
1676 # failure down the line if handling many targets
1678 return undef unless @$targets;
1679 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1682 __PACKAGE__->register_method(
1683 method => "user_payments_list",
1684 api_name => "open-ils.circ.user_payments.filtered.batch",
1687 desc => q/Returns a fleshed, date-limited set of all payments a user
1688 has made. By default, ordered by payment date. Optionally
1689 ordered by other columns in the top-level "mp" object/,
1691 {desc => 'Authentication token', type => 'string'},
1692 {desc => 'User ID', type => 'number'},
1693 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1695 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1696 and the related fully-realized payment object (e.g money.cash_payment)/}
1700 sub user_payments_list {
1701 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1703 my $e = new_editor(authtoken => $auth);
1704 return $e->event unless $e->checkauth;
1706 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1707 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1709 $order_by ||= ['payment_ts'];
1711 # all payments by user, between start_date and end_date
1712 my $payments = $e->json_query({
1713 select => {mp => ['id']},
1717 fkey => 'xact', field => 'id'}
1721 '+mbt' => {usr => $user_id},
1722 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1724 order_by => {mp => $order_by}
1727 for my $payment_id (@$payments) {
1728 my $payment = $e->retrieve_money_payment([
1736 'credit_card_payment',
1751 $conn->respond($payment);
1758 __PACKAGE__->register_method(
1759 method => "retrieve_circ_chain",
1760 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1763 desc => q/Given a circulation, this returns all circulation objects
1764 that are part of the same chain of renewals./,
1766 {desc => 'Authentication token', type => 'string'},
1767 {desc => 'Circ ID', type => 'number'},
1769 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1773 __PACKAGE__->register_method(
1774 method => "retrieve_circ_chain",
1775 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1777 desc => q/Given a circulation, this returns a summary of the circulation objects
1778 that are part of the same chain of renewals./,
1780 {desc => 'Authentication token', type => 'string'},
1781 {desc => 'Circ ID', type => 'number'},
1783 return => {desc => q/Circulation Chain Summary/}
1787 sub retrieve_circ_chain {
1788 my($self, $conn, $auth, $circ_id) = @_;
1790 my $e = new_editor(authtoken => $auth);
1791 return $e->event unless $e->checkauth;
1792 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1794 if($self->api_name =~ /summary/) {
1795 return $U->create_circ_chain_summary($e, $circ_id);
1799 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1801 for my $circ_info (@$chain) {
1802 my $circ = Fieldmapper::action::circulation->new;
1803 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1804 $conn->respond($circ);
1811 __PACKAGE__->register_method(
1812 method => "retrieve_prev_circ_chain",
1813 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1816 desc => q/Given a circulation, this returns all circulation objects
1817 that are part of the previous chain of renewals./,
1819 {desc => 'Authentication token', type => 'string'},
1820 {desc => 'Circ ID', type => 'number'},
1822 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1826 __PACKAGE__->register_method(
1827 method => "retrieve_prev_circ_chain",
1828 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1830 desc => q/Given a circulation, this returns a summary of the circulation objects
1831 that are part of the previous chain of renewals./,
1833 {desc => 'Authentication token', type => 'string'},
1834 {desc => 'Circ ID', type => 'number'},
1836 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1840 sub retrieve_prev_circ_chain {
1841 my($self, $conn, $auth, $circ_id) = @_;
1843 my $e = new_editor(authtoken => $auth);
1844 return $e->event unless $e->checkauth;
1845 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1847 if($self->api_name =~ /summary/) {
1848 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1849 my $target_copy = $$first_circ{'target_copy'};
1850 my $usr = $$first_circ{'usr'};
1851 my $last_circ_from_prev_chain = $e->json_query({
1852 'select' => { 'circ' => ['id','usr'] },
1855 target_copy => $target_copy,
1856 xact_start => { '<' => $$first_circ{'xact_start'} }
1858 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1861 return undef unless $last_circ_from_prev_chain;
1862 return undef unless $$last_circ_from_prev_chain{'id'};
1863 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1864 return undef unless $sum;
1865 my $obj = Fieldmapper::action::circ_chain_summary->new;
1866 $obj->$_($sum->{$_}) for keys %$sum;
1867 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1871 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1872 my $target_copy = $$first_circ{'target_copy'};
1873 my $last_circ_from_prev_chain = $e->json_query({
1874 'select' => { 'circ' => ['id'] },
1877 target_copy => $target_copy,
1878 xact_start => { '<' => $$first_circ{'xact_start'} }
1880 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1883 return undef unless $last_circ_from_prev_chain;
1884 return undef unless $$last_circ_from_prev_chain{'id'};
1885 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1887 for my $circ_info (@$chain) {
1888 my $circ = Fieldmapper::action::circulation->new;
1889 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1890 $conn->respond($circ);
1898 __PACKAGE__->register_method(
1899 method => "get_copy_due_date",
1900 api_name => "open-ils.circ.copy.due_date.retrieve",
1903 Given a copy ID, returns the due date for the copy if it's
1904 currently circulating. Otherwise, returns null. Note, this is a public
1905 method requiring no authentication. Only the due date is exposed.
1908 {desc => 'Copy ID', type => 'number'}
1910 return => {desc => q/
1911 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1916 sub get_copy_due_date {
1917 my($self, $conn, $copy_id) = @_;
1918 my $e = new_editor();
1920 my $circ = $e->json_query({
1921 select => {circ => ['due_date']},
1924 target_copy => $copy_id,
1925 checkin_time => undef,
1927 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1928 {stop_fines => undef}
1932 })->[0] or return undef;
1934 return $circ->{due_date};
1941 # {"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}}