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::CreditCard;
12 use OpenILS::Application::Circ::Money;
13 use OpenILS::Application::Circ::NonCat;
14 use OpenILS::Application::Circ::CopyLocations;
15 use OpenILS::Application::Circ::CircCommon;
18 use DateTime::Format::ISO8601;
20 use OpenILS::Application::AppUtils;
22 use OpenSRF::Utils qw/:datetime/;
23 use OpenSRF::AppSession;
24 use OpenILS::Utils::ModsParser;
26 use OpenSRF::EX qw(:try);
27 use OpenSRF::Utils::Logger qw(:logger);
28 use OpenILS::Utils::Fieldmapper;
29 use OpenILS::Utils::CStoreEditor q/:funcs/;
30 use OpenILS::Const qw/:const/;
31 use OpenSRF::Utils::SettingsClient;
32 use OpenILS::Application::Cat::AssetCommon;
34 my $apputils = "OpenILS::Application::AppUtils";
37 my $holdcode = "OpenILS::Application::Circ::Holds";
39 # ------------------------------------------------------------------------
40 # Top level Circ package;
41 # ------------------------------------------------------------------------
45 OpenILS::Application::Circ::Circulate->initialize();
49 __PACKAGE__->register_method(
50 method => 'retrieve_circ',
52 api_name => 'open-ils.circ.retrieve',
54 Retrieve a circ object by id
55 @param authtoken Login session key
56 @pararm circid The id of the circ object
60 my( $s, $c, $a, $i ) = @_;
61 my $e = new_editor(authtoken => $a);
62 return $e->event unless $e->checkauth;
63 my $circ = $e->retrieve_action_circulation($i) or return $e->event;
64 if( $e->requestor->id ne $circ->usr ) {
65 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
71 __PACKAGE__->register_method(
72 method => 'fetch_circ_mods',
73 api_name => 'open-ils.circ.circ_modifier.retrieve.all');
75 my($self, $conn, $args) = @_;
76 my $mods = new_editor()->retrieve_all_config_circ_modifier;
77 return [ map {$_->code} @$mods ] unless $$args{full};
81 __PACKAGE__->register_method(
82 method => 'ranged_billing_types',
83 api_name => 'open-ils.circ.billing_type.ranged.retrieve.all');
85 sub ranged_billing_types {
86 my($self, $conn, $auth, $org_id, $depth) = @_;
87 my $e = new_editor(authtoken => $auth);
88 return $e->event unless $e->checkauth;
89 return $e->event unless $e->allowed('VIEW_BILLING_TYPE', $org_id);
90 return $e->search_config_billing_type(
91 {owner => $U->get_org_full_path($org_id, $depth)});
96 # ------------------------------------------------------------------------
97 # Returns an array of {circ, record} hashes checked out by the user.
98 # ------------------------------------------------------------------------
99 __PACKAGE__->register_method(
100 method => "checkouts_by_user",
101 api_name => "open-ils.circ.actor.user.checked_out",
103 NOTES => <<" NOTES");
104 Returns a list of open circulations as a pile of objects. Each object
105 contains the relevant copy, circ, and record
108 sub checkouts_by_user {
109 my($self, $client, $auth, $user_id) = @_;
111 my $e = new_editor(authtoken=>$auth);
112 return $e->event unless $e->checkauth;
114 my $circ_ids = $e->search_action_circulation(
116 checkin_time => undef,
118 {stop_fines => undef},
119 {stop_fines => ['MAXFINES','LONGOVERDUE']}
125 for my $id (@$circ_ids) {
126 my $circ = $e->retrieve_action_circulation([
130 circ => ['target_copy'],
131 acp => ['call_number'],
137 # un-flesh for consistency
138 my $c = $circ->target_copy;
139 $circ->target_copy($c->id);
141 my $cn = $c->call_number;
142 $c->call_number($cn->id);
150 record => $U->record_to_mvr($t)
160 __PACKAGE__->register_method(
161 method => "checkouts_by_user_slim",
162 api_name => "open-ils.circ.actor.user.checked_out.slim",
163 NOTES => <<" NOTES");
164 Returns a list of open circulation objects
168 sub checkouts_by_user_slim {
169 my( $self, $client, $user_session, $user_id ) = @_;
171 my( $requestor, $target, $copy, $record, $evt );
173 ( $requestor, $target, $evt ) =
174 $apputils->checkses_requestor( $user_session, $user_id, 'VIEW_CIRCULATIONS');
177 $logger->debug( 'User ' . $requestor->id .
178 " retrieving checked out items for user " . $target->id );
180 # XXX Make the call correct..
181 return $apputils->simplereq(
183 "open-ils.cstore.direct.action.open_circulation.search.atomic",
184 { usr => $target->id, checkin_time => undef } );
185 # { usr => $target->id } );
189 __PACKAGE__->register_method(
190 method => "checkouts_by_user_opac",
191 api_name => "open-ils.circ.actor.user.checked_out.opac",);
194 sub checkouts_by_user_opac {
195 my( $self, $client, $auth, $user_id ) = @_;
197 my $e = new_editor( authtoken => $auth );
198 return $e->event unless $e->checkauth;
199 $user_id ||= $e->requestor->id;
200 return $e->event unless
201 my $patron = $e->retrieve_actor_user($user_id);
204 my $search = {usr => $user_id, stop_fines => undef};
206 if( $user_id ne $e->requestor->id ) {
207 $data = $e->search_action_circulation(
208 $search, {checkperm=>1, permorg=>$patron->home_ou})
212 $data = $e->search_action_circulation($search);
219 __PACKAGE__->register_method(
220 method => "title_from_transaction",
221 api_name => "open-ils.circ.circ_transaction.find_title",
222 NOTES => <<" NOTES");
223 Returns a mods object for the title that is linked to from the
224 copy from the hold that created the given transaction
227 sub title_from_transaction {
228 my( $self, $client, $login_session, $transactionid ) = @_;
230 my( $user, $circ, $title, $evt );
232 ( $user, $evt ) = $apputils->checkses( $login_session );
235 ( $circ, $evt ) = $apputils->fetch_circulation($transactionid);
238 ($title, $evt) = $apputils->fetch_record_by_copy($circ->target_copy);
241 return $apputils->record_to_mvr($title);
244 __PACKAGE__->register_method(
245 method => "staff_age_to_lost",
246 api_name => "open-ils.circ.circulation.age_to_lost",
249 This fires a circ.staff_age_to_lost Action-Trigger event against all
250 overdue circulations in scope of the specified context library and
251 user profile, which effectively marks the associated items as Lost.
252 This is likely to be done at the end of a semester in an academic
255 @param args : circ_lib, user_profile
259 sub staff_age_to_lost {
260 my( $self, $conn, $auth, $args ) = @_;
261 my $e = new_editor(authtoken=>$auth);
262 return $e->event unless $e->checkauth;
263 return $e->event unless $e->allowed('SET_CIRC_LOST', $args->{'circ_lib'});
265 my $orgs = $U->get_org_descendants($args->{'circ_lib'});
266 my $profiles = $U->fetch_permission_group_descendants($args->{'user_profile'});
268 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
270 my $method = 'open-ils.trigger.passive.event.autocreate.batch';
271 my $hook = 'circ.staff_age_to_lost';
272 my $context_org = 'circ_lib';
273 my $opt_granularity = undef;
275 "checkin_time" => undef,
276 "due_date" => { "<" => "now" },
278 { "stop_fines" => ["MAXFINES", "LONGOVERDUE"] }, # FIXME: CLAIMSRETURNED also?
279 { "stop_fines" => undef }
283 "select" => {"au" => ["id"]},
286 "profile" => $profiles,
287 "id" => { "=" => {"+circ" => "usr"} }
291 "select" => {"aou" => ["id"]},
295 {"id" => { "=" => {"+circ" => "circ_lib"} }},
302 my $req_timeout = 10800;
303 my $chunk_size = 100;
306 my $req = $ses->request($method, $hook, $context_org, $filter, $opt_granularity);
307 my @event_ids; my @chunked_ids;
308 while (my $resp = $req->recv(timeout => $req_timeout)) {
309 push(@event_ids, $resp->content);
310 push(@chunked_ids, $resp->content);
311 if (scalar(@chunked_ids) > $chunk_size) {
312 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
316 if (scalar(@chunked_ids) > 0) {
317 $conn->respond({'progress'=>$progress++}); # 'event_ids'=>@chunked_ids
321 $logger->info("staff_age_to_lost: created ".scalar(@event_ids)." events for circ.staff_age_to_lost");
322 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>scalar(@event_ids)});
323 } elsif($req->complete) {
324 $logger->info("staff_age_to_lost: no events to create for circ.staff_age_to_lost");
325 $conn->respond_complete({'total_progress'=>$progress-1,'created'=>0});
327 $logger->warn("staff_age_to_lost: timeout occurred during event creation for circ.staff_age_to_lost");
328 $conn->respond_complete({'total_progress'=>$progress-1,'error'=>'timeout'});
335 __PACKAGE__->register_method(
336 method => "new_set_circ_lost",
337 api_name => "open-ils.circ.circulation.set_lost",
339 Sets the copy and related open circulation to lost
341 @param args : barcode
346 # ---------------------------------------------------------------------
347 # Sets a circulation to lost. updates copy status to lost
348 # applies copy and/or prcoessing fees depending on org settings
349 # ---------------------------------------------------------------------
350 sub new_set_circ_lost {
351 my( $self, $conn, $auth, $args ) = @_;
353 my $e = new_editor(authtoken=>$auth, xact=>1);
354 return $e->die_event unless $e->checkauth;
356 my $copy = $e->search_asset_copy({barcode=>$$args{barcode}, deleted=>'f'})->[0]
357 or return $e->die_event;
359 my $evt = OpenILS::Application::Cat::AssetCommon->set_item_lost($e, $copy->id);
367 __PACKAGE__->register_method(
368 method => "set_circ_claims_returned",
369 api_name => "open-ils.circ.circulation.set_claims_returned",
371 desc => q/Sets the circ for a given item as claims returned
372 If a backdate is provided, overdue fines will be voided
373 back to the backdate/,
375 {desc => 'Authentication token', type => 'string'},
376 {desc => 'Arguments, including "barcode" and optional "backdate"', type => 'object'}
378 return => {desc => q/1 on success, failure event on error, and
379 PATRON_EXCEEDS_CLAIMS_RETURN_COUNT if the patron exceeds the
380 configured claims return maximum/}
384 __PACKAGE__->register_method(
385 method => "set_circ_claims_returned",
386 api_name => "open-ils.circ.circulation.set_claims_returned.override",
388 desc => q/This adds support for overrideing the configured max
389 claims returned amount.
390 @see open-ils.circ.circulation.set_claims_returned./,
394 sub set_circ_claims_returned {
395 my( $self, $conn, $auth, $args, $oargs ) = @_;
397 my $e = new_editor(authtoken=>$auth, xact=>1);
398 return $e->die_event unless $e->checkauth;
400 $oargs = { all => 1 } unless defined $oargs;
402 my $barcode = $$args{barcode};
403 my $backdate = $$args{backdate};
405 my $copy = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'})->[0]
406 or return $e->die_event;
408 my $circ = $e->search_action_circulation(
409 {checkin_time => undef, target_copy => $copy->id})->[0]
410 or return $e->die_event;
412 $backdate = $circ->due_date if $$args{use_due_date};
414 $logger->info("marking circ for item $barcode as claims returned".
415 (($backdate) ? " with backdate $backdate" : ''));
417 my $patron = $e->retrieve_actor_user($circ->usr);
418 my $max_count = $U->ou_ancestor_setting_value(
419 $circ->circ_lib, 'circ.max_patron_claim_return_count', $e);
421 # If the patron has too instances of many claims returned,
422 # require an override to continue. A configured max of
423 # 0 means all attempts require an override
424 if(defined $max_count and $patron->claims_returned_count >= $max_count) {
426 if($self->api_name =~ /override/ && ($oargs->{all} || grep { $_ eq 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT' } @{$oargs->{events}})) {
428 # see if we're allowed to override
429 return $e->die_event unless
430 $e->allowed('SET_CIRC_CLAIMS_RETURNED.override', $circ->circ_lib);
434 # exit early and return the max claims return event
436 return OpenILS::Event->new(
437 'PATRON_EXCEEDS_CLAIMS_RETURN_COUNT',
439 patron_count => $patron->claims_returned_count,
440 max_count => $max_count
446 $e->allowed('SET_CIRC_CLAIMS_RETURNED', $circ->circ_lib)
447 or return $e->die_event;
449 $circ->stop_fines(OILS_STOP_FINES_CLAIMSRETURNED);
450 $circ->stop_fines_time('now') unless $circ->stop_fines_time;
453 $backdate = cleanse_ISO8601($backdate);
455 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
456 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($backdate);
457 $backdate = $new_date->ymd . 'T' . $original_date->strftime('%T%z');
459 # clean it up once again; need a : in the timezone offset. E.g. -06:00 not -0600
460 $backdate = cleanse_ISO8601($backdate);
462 # make it look like the circ stopped at the cliams returned time
463 $circ->stop_fines_time($backdate);
464 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate, note => 'System: OVERDUE REVERSED FOR CLAIMS-RETURNED', force_zero => 1});
468 $e->update_action_circulation($circ) or return $e->die_event;
470 # see if there is a configured post-claims-return copy status
471 if(my $stat = $U->ou_ancestor_setting_value($circ->circ_lib, 'circ.claim_return.copy_status')) {
472 $copy->status($stat);
473 $copy->edit_date('now');
474 $copy->editor($e->requestor->id);
475 $e->update_asset_copy($copy) or return $e->die_event;
478 # Check if the copy circ lib wants lost fees voided on claims
480 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_on_claimsreturned', $e))) {
481 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
492 # Check if the copy circ lib wants lost processing fees voided on
494 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_lost_proc_fee_on_claimsreturned', $e))) {
495 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
506 # Check if the copy circ lib wants longoverdue fees voided on claims
508 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_on_claimsreturned', $e))) {
509 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
520 # Check if the copy circ lib wants longoverdue processing fees voided on
522 if ($U->is_true($U->ou_ancestor_setting_value($copy->circ_lib, 'circ.void_longoverdue_proc_fee_on_claimsreturned', $e))) {
523 my $result = OpenILS::Application::Circ::CircCommon->void_lost(
539 __PACKAGE__->register_method(
540 method => "post_checkin_backdate_circ",
541 api_name => "open-ils.circ.post_checkin_backdate",
543 desc => q/Back-date an already checked in circulation/,
545 {desc => 'Authentication token', type => 'string'},
546 {desc => 'Circ ID', type => 'number'},
547 {desc => 'ISO8601 backdate', type => 'string'},
549 return => {desc => q/1 on success, failure event on error/}
553 __PACKAGE__->register_method(
554 method => "post_checkin_backdate_circ",
555 api_name => "open-ils.circ.post_checkin_backdate.batch",
558 desc => q/@see open-ils.circ.post_checkin_backdate. Batch mode/,
560 {desc => 'Authentication token', type => 'string'},
561 {desc => 'List of Circ ID', type => 'array'},
562 {desc => 'ISO8601 backdate', type => 'string'},
564 return => {desc => q/Set of: 1 on success, failure event on error/}
569 sub post_checkin_backdate_circ {
570 my( $self, $conn, $auth, $circ_id, $backdate ) = @_;
571 my $e = new_editor(authtoken=>$auth);
572 return $e->die_event unless $e->checkauth;
573 if($self->api_name =~ /batch/) {
574 foreach my $c (@$circ_id) {
575 $conn->respond(post_checkin_backdate_circ_impl($e, $c, $backdate));
578 $conn->respond_complete(post_checkin_backdate_circ_impl($e, $circ_id, $backdate));
586 sub post_checkin_backdate_circ_impl {
587 my($e, $circ_id, $backdate) = @_;
591 my $circ = $e->retrieve_action_circulation($circ_id)
592 or return $e->die_event;
594 # anyone with checkin perms can backdate (more restrictive?)
595 return $e->die_event unless $e->allowed('COPY_CHECKIN', $circ->circ_lib);
597 # don't allow back-dating an open circulation
598 return OpenILS::Event->new('BAD_PARAMS') unless
599 $backdate and $circ->checkin_time;
601 # update the checkin and stop_fines times to reflect the new backdate
602 $circ->stop_fines_time(cleanse_ISO8601($backdate));
603 $circ->checkin_time(cleanse_ISO8601($backdate));
604 $e->update_action_circulation($circ) or return $e->die_event;
606 # now void the overdues "erased" by the back-dating
607 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {backdate => $backdate, note => 'System: OVERDUE REVERSED FOR BACKDATE'});
610 # If the circ was closed before and the balance owned !=0, re-open the transaction
611 $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
620 __PACKAGE__->register_method (
621 method => 'set_circ_due_date',
622 api_name => 'open-ils.circ.circulation.due_date.update',
624 Updates the due_date on the given circ
626 @param circid The id of the circ to update
627 @param date The timestamp of the new due date
631 sub set_circ_due_date {
632 my( $self, $conn, $auth, $circ_id, $date ) = @_;
634 my $e = new_editor(xact=>1, authtoken=>$auth);
635 return $e->die_event unless $e->checkauth;
636 my $circ = $e->retrieve_action_circulation($circ_id)
637 or return $e->die_event;
639 return $e->die_event unless $e->allowed('CIRC_OVERRIDE_DUE_DATE', $circ->circ_lib);
640 $date = cleanse_ISO8601($date);
642 if (!(interval_to_seconds($circ->duration) % 86400)) { # duration is divisible by days
643 my $original_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($circ->due_date));
644 my $new_date = DateTime::Format::ISO8601->new->parse_datetime($date);
645 $date = cleanse_ISO8601( $new_date->ymd . 'T' . $original_date->strftime('%T%z') );
648 $circ->due_date($date);
649 $e->update_action_circulation($circ) or return $e->die_event;
656 __PACKAGE__->register_method(
657 method => "create_in_house_use",
658 api_name => 'open-ils.circ.in_house_use.create',
660 Creates an in-house use action.
661 @param $authtoken The login session key
662 @param params A hash of params including
663 'location' The org unit id where the in-house use occurs
664 'copyid' The copy in question
665 'count' The number of in-house uses to apply to this copy
666 @return An array of id's representing the id's of the newly created
667 in-house use objects or an event on an error
670 __PACKAGE__->register_method(
671 method => "create_in_house_use",
672 api_name => 'open-ils.circ.non_cat_in_house_use.create',
676 sub create_in_house_use {
677 my( $self, $client, $auth, $params ) = @_;
680 my $org = $params->{location};
681 my $copyid = $params->{copyid};
682 my $count = $params->{count} || 1;
683 my $nc_type = $params->{non_cat_type};
684 my $use_time = $params->{use_time} || 'now';
686 my $e = new_editor(xact=>1,authtoken=>$auth);
687 return $e->event unless $e->checkauth;
688 return $e->event unless $e->allowed('CREATE_IN_HOUSE_USE');
690 my $non_cat = 1 if $self->api_name =~ /non_cat/;
694 $copy = $e->retrieve_asset_copy($copyid) or return $e->event;
696 $copy = $e->search_asset_copy({barcode=>$params->{barcode}, deleted => 'f'})->[0]
702 if( $use_time ne 'now' ) {
703 $use_time = cleanse_ISO8601($use_time);
704 $logger->debug("in_house_use setting use time to $use_time");
715 $ihu = Fieldmapper::action::non_cat_in_house_use->new;
716 $ihu->item_type($nc_type);
717 $method = 'open-ils.storage.direct.action.non_cat_in_house_use.create';
718 $cmeth = "create_action_non_cat_in_house_use";
721 $ihu = Fieldmapper::action::in_house_use->new;
723 $method = 'open-ils.storage.direct.action.in_house_use.create';
724 $cmeth = "create_action_in_house_use";
727 $ihu->staff($e->requestor->id);
728 $ihu->org_unit($org);
729 $ihu->use_time($use_time);
731 $ihu = $e->$cmeth($ihu) or return $e->event;
732 push( @ids, $ihu->id );
743 __PACKAGE__->register_method(
744 method => "view_circs",
745 api_name => "open-ils.circ.copy_checkout_history.retrieve",
747 Retrieves the last X circs for a given copy
748 @param authtoken The login session key
749 @param copyid The copy to check
750 @param count How far to go back in the item history
751 @return An array of circ ids
754 # ----------------------------------------------------------------------
755 # Returns $count most recent circs. If count exceeds the configured
756 # max, use the configured max instead
757 # ----------------------------------------------------------------------
759 my( $self, $client, $authtoken, $copyid, $count ) = @_;
761 my $e = new_editor(authtoken => $authtoken);
762 return $e->event unless $e->checkauth;
764 my $copy = $e->retrieve_asset_copy([
767 flesh_fields => {acp => ['call_number']}
769 ]) or return $e->event;
771 return $e->event unless $e->allowed(
772 'VIEW_COPY_CHECKOUT_HISTORY',
773 ($copy->call_number == OILS_PRECAT_CALL_NUMBER) ?
774 $copy->circ_lib : $copy->call_number->owning_lib);
776 my $max_history = $U->ou_ancestor_setting_value(
777 $e->requestor->ws_ou, 'circ.item_checkout_history.max', $e);
779 if(defined $max_history) {
780 $count = $max_history unless defined $count and $count < $max_history;
782 $count = 4 unless defined $count;
785 return $e->search_action_circulation([
786 {target_copy => $copyid},
787 {limit => $count, order_by => { circ => "xact_start DESC" }}
792 __PACKAGE__->register_method(
793 method => "circ_count",
794 api_name => "open-ils.circ.circulation.count",
796 Returns the number of times the item has circulated
797 @param copyid The copy to check
801 my( $self, $client, $copyid ) = @_;
803 my $count = new_editor()->json_query({
812 where => {'+circbyyr' => {copy => $copyid}}
824 __PACKAGE__->register_method(
825 method => 'fetch_notes',
827 api_name => 'open-ils.circ.copy_note.retrieve.all',
829 Returns an array of copy note objects.
830 @param args A named hash of parameters including:
831 authtoken : Required if viewing non-public notes
832 itemid : The id of the item whose notes we want to retrieve
833 pub : True if all the caller wants are public notes
834 @return An array of note objects
837 __PACKAGE__->register_method(
838 method => 'fetch_notes',
839 api_name => 'open-ils.circ.call_number_note.retrieve.all',
840 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
842 __PACKAGE__->register_method(
843 method => 'fetch_notes',
844 api_name => 'open-ils.circ.title_note.retrieve.all',
845 signature => q/@see open-ils.circ.copy_note.retrieve.all/);
848 # NOTE: VIEW_COPY/VOLUME/TITLE_NOTES perms should always be global
850 my( $self, $connection, $args ) = @_;
852 my $id = $$args{itemid};
853 my $authtoken = $$args{authtoken};
856 if( $self->api_name =~ /copy/ ) {
858 return $U->cstorereq(
859 'open-ils.cstore.direct.asset.copy_note.search.atomic',
860 { owning_copy => $id, pub => 't' } );
862 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
864 return $U->cstorereq(
865 'open-ils.cstore.direct.asset.copy_note.search.atomic', {owning_copy => $id} );
868 } elsif( $self->api_name =~ /call_number/ ) {
870 return $U->cstorereq(
871 'open-ils.cstore.direct.asset.call_number_note.search.atomic',
872 { call_number => $id, pub => 't' } );
874 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_VOLUME_NOTES');
876 return $U->cstorereq(
877 'open-ils.cstore.direct.asset.call_number_note.search.atomic', { call_number => $id } );
880 } elsif( $self->api_name =~ /title/ ) {
882 return $U->cstorereq(
883 'open-ils.cstore.direct.bilbio.record_note.search.atomic',
884 { record => $id, pub => 't' } );
886 ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_TITLE_NOTES');
888 return $U->cstorereq(
889 'open-ils.cstore.direct.biblio.record_note.search.atomic', { record => $id } );
896 __PACKAGE__->register_method(
897 method => 'has_notes',
898 api_name => 'open-ils.circ.copy.has_notes');
899 __PACKAGE__->register_method(
900 method => 'has_notes',
901 api_name => 'open-ils.circ.call_number.has_notes');
902 __PACKAGE__->register_method(
903 method => 'has_notes',
904 api_name => 'open-ils.circ.title.has_notes');
908 my( $self, $conn, $authtoken, $id ) = @_;
909 my $editor = new_editor(authtoken => $authtoken);
910 return $editor->event unless $editor->checkauth;
912 my $n = $editor->search_asset_copy_note(
913 {owning_copy=>$id}, {idlist=>1}) if $self->api_name =~ /copy/;
915 $n = $editor->search_asset_call_number_note(
916 {call_number=>$id}, {idlist=>1}) if $self->api_name =~ /call_number/;
918 $n = $editor->search_biblio_record_note(
919 {record=>$id}, {idlist=>1}) if $self->api_name =~ /title/;
926 __PACKAGE__->register_method(
927 method => 'create_copy_note',
928 api_name => 'open-ils.circ.copy_note.create',
930 Creates a new copy note
931 @param authtoken The login session key
932 @param note The note object to create
933 @return The id of the new note object
936 sub create_copy_note {
937 my( $self, $connection, $authtoken, $note ) = @_;
939 my $e = new_editor(xact=>1, authtoken=>$authtoken);
940 return $e->event unless $e->checkauth;
941 my $copy = $e->retrieve_asset_copy(
945 flesh_fields => { 'acp' => ['call_number'] }
950 return $e->event unless
951 $e->allowed('CREATE_COPY_NOTE', $copy->call_number->owning_lib);
953 $note->create_date('now');
954 $note->creator($e->requestor->id);
955 $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
958 $e->create_asset_copy_note($note) or return $e->event;
964 __PACKAGE__->register_method(
965 method => 'delete_copy_note',
966 api_name => 'open-ils.circ.copy_note.delete',
968 Deletes an existing copy note
969 @param authtoken The login session key
970 @param noteid The id of the note to delete
971 @return 1 on success - Event otherwise.
973 sub delete_copy_note {
974 my( $self, $conn, $authtoken, $noteid ) = @_;
976 my $e = new_editor(xact=>1, authtoken=>$authtoken);
977 return $e->die_event unless $e->checkauth;
979 my $note = $e->retrieve_asset_copy_note([
983 'acpn' => [ 'owning_copy' ],
984 'acp' => [ 'call_number' ],
987 ]) or return $e->die_event;
989 if( $note->creator ne $e->requestor->id ) {
990 return $e->die_event unless
991 $e->allowed('DELETE_COPY_NOTE', $note->owning_copy->call_number->owning_lib);
994 $e->delete_asset_copy_note($note) or return $e->die_event;
1000 __PACKAGE__->register_method(
1001 method => 'age_hold_rules',
1002 api_name => 'open-ils.circ.config.rules.age_hold_protect.retrieve.all',
1005 sub age_hold_rules {
1006 my( $self, $conn ) = @_;
1007 return new_editor()->retrieve_all_config_rules_age_hold_protect();
1012 __PACKAGE__->register_method(
1013 method => 'copy_details_barcode',
1015 api_name => 'open-ils.circ.copy_details.retrieve.barcode');
1016 sub copy_details_barcode {
1017 my( $self, $conn, $auth, $barcode ) = @_;
1018 my $e = new_editor();
1019 my $cid = $e->search_asset_copy({barcode=>$barcode, deleted=>'f'}, {idlist=>1})->[0];
1020 return $e->event unless $cid;
1021 return copy_details( $self, $conn, $auth, $cid );
1025 __PACKAGE__->register_method(
1026 method => 'copy_details',
1027 api_name => 'open-ils.circ.copy_details.retrieve');
1030 my( $self, $conn, $auth, $copy_id ) = @_;
1031 my $e = new_editor(authtoken=>$auth);
1032 return $e->event unless $e->checkauth;
1034 my $flesh = { flesh => 1 };
1036 my $copy = $e->retrieve_asset_copy(
1042 acp => ['call_number','parts','peer_record_maps','floating'],
1043 acn => ['record','prefix','suffix','label_class']
1046 ]) or return $e->event;
1049 # De-flesh the copy for backwards compatibility
1051 my $vol = $copy->call_number;
1053 $copy->call_number($vol->id);
1054 my $record = $vol->record;
1056 $vol->record($record->id);
1057 $mvr = $U->record_to_mvr($record);
1062 my $hold = $e->search_action_hold_request(
1064 current_copy => $copy_id,
1065 capture_time => { "!=" => undef },
1066 fulfillment_time => undef,
1067 cancel_time => undef,
1071 OpenILS::Application::Circ::Holds::flesh_hold_transits([$hold]) if $hold;
1073 my $transit = $e->search_action_transit_copy(
1074 { target_copy => $copy_id, dest_recv_time => undef } )->[0];
1076 # find the latest circ, open or closed
1077 my $circ = $e->search_action_circulation(
1079 { target_copy => $copy_id },
1085 'checkin_workstation',
1088 'recurring_fine_rule'
1091 order_by => { circ => 'xact_start desc' },
1101 transit => $transit,
1111 __PACKAGE__->register_method(
1112 method => 'mark_item',
1113 api_name => 'open-ils.circ.mark_item_damaged',
1115 Changes the status of a copy to "damaged". Requires MARK_ITEM_DAMAGED permission.
1116 @param authtoken The login session key
1117 @param copy_id The ID of the copy to mark as damaged
1118 @return 1 on success - Event otherwise.
1121 __PACKAGE__->register_method(
1122 method => 'mark_item',
1123 api_name => 'open-ils.circ.mark_item_missing',
1125 Changes the status of a copy to "missing". Requires MARK_ITEM_MISSING permission.
1126 @param authtoken The login session key
1127 @param copy_id The ID of the copy to mark as missing
1128 @return 1 on success - Event otherwise.
1131 __PACKAGE__->register_method(
1132 method => 'mark_item',
1133 api_name => 'open-ils.circ.mark_item_bindery',
1135 Changes the status of a copy to "bindery". Requires MARK_ITEM_BINDERY permission.
1136 @param authtoken The login session key
1137 @param copy_id The ID of the copy to mark as bindery
1138 @return 1 on success - Event otherwise.
1141 __PACKAGE__->register_method(
1142 method => 'mark_item',
1143 api_name => 'open-ils.circ.mark_item_on_order',
1145 Changes the status of a copy to "on order". Requires MARK_ITEM_ON_ORDER permission.
1146 @param authtoken The login session key
1147 @param copy_id The ID of the copy to mark as on order
1148 @return 1 on success - Event otherwise.
1151 __PACKAGE__->register_method(
1152 method => 'mark_item',
1153 api_name => 'open-ils.circ.mark_item_ill',
1155 Changes the status of a copy to "inter-library loan". Requires MARK_ITEM_ILL permission.
1156 @param authtoken The login session key
1157 @param copy_id The ID of the copy to mark as inter-library loan
1158 @return 1 on success - Event otherwise.
1161 __PACKAGE__->register_method(
1162 method => 'mark_item',
1163 api_name => 'open-ils.circ.mark_item_cataloging',
1165 Changes the status of a copy to "cataloging". Requires MARK_ITEM_CATALOGING permission.
1166 @param authtoken The login session key
1167 @param copy_id The ID of the copy to mark as cataloging
1168 @return 1 on success - Event otherwise.
1171 __PACKAGE__->register_method(
1172 method => 'mark_item',
1173 api_name => 'open-ils.circ.mark_item_reserves',
1175 Changes the status of a copy to "reserves". Requires MARK_ITEM_RESERVES permission.
1176 @param authtoken The login session key
1177 @param copy_id The ID of the copy to mark as reserves
1178 @return 1 on success - Event otherwise.
1181 __PACKAGE__->register_method(
1182 method => 'mark_item',
1183 api_name => 'open-ils.circ.mark_item_discard',
1185 Changes the status of a copy to "discard". Requires MARK_ITEM_DISCARD permission.
1186 @param authtoken The login session key
1187 @param copy_id The ID of the copy to mark as discard
1188 @return 1 on success - Event otherwise.
1193 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1194 my $e = new_editor(authtoken=>$auth, xact =>1);
1195 return $e->die_event unless $e->checkauth;
1198 my $copy = $e->retrieve_asset_copy([
1200 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1201 or return $e->die_event;
1204 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1205 $copy->circ_lib : $copy->call_number->owning_lib;
1207 my $perm = 'MARK_ITEM_MISSING';
1208 my $stat = OILS_COPY_STATUS_MISSING;
1210 if( $self->api_name =~ /damaged/ ) {
1211 $perm = 'MARK_ITEM_DAMAGED';
1212 $stat = OILS_COPY_STATUS_DAMAGED;
1213 my $evt = handle_mark_damaged($e, $copy, $owning_lib, $args);
1214 return $evt if $evt;
1216 } elsif ( $self->api_name =~ /bindery/ ) {
1217 $perm = 'MARK_ITEM_BINDERY';
1218 $stat = OILS_COPY_STATUS_BINDERY;
1219 } elsif ( $self->api_name =~ /on_order/ ) {
1220 $perm = 'MARK_ITEM_ON_ORDER';
1221 $stat = OILS_COPY_STATUS_ON_ORDER;
1222 } elsif ( $self->api_name =~ /ill/ ) {
1223 $perm = 'MARK_ITEM_ILL';
1224 $stat = OILS_COPY_STATUS_ILL;
1225 } elsif ( $self->api_name =~ /cataloging/ ) {
1226 $perm = 'MARK_ITEM_CATALOGING';
1227 $stat = OILS_COPY_STATUS_CATALOGING;
1228 } elsif ( $self->api_name =~ /reserves/ ) {
1229 $perm = 'MARK_ITEM_RESERVES';
1230 $stat = OILS_COPY_STATUS_RESERVES;
1231 } elsif ( $self->api_name =~ /discard/ ) {
1232 $perm = 'MARK_ITEM_DISCARD';
1233 $stat = OILS_COPY_STATUS_DISCARD;
1236 # caller may proceed if either perm is allowed
1237 return $e->die_event unless $e->allowed([$perm, 'UPDATE_COPY'], $owning_lib);
1239 $copy->status($stat);
1240 $copy->edit_date('now');
1241 $copy->editor($e->requestor->id);
1243 $e->update_asset_copy($copy) or return $e->die_event;
1245 my $holds = $e->search_action_hold_request(
1247 current_copy => $copy->id,
1248 fulfillment_time => undef,
1249 cancel_time => undef,
1255 if( $self->api_name =~ /damaged/ ) {
1256 # now that we've committed the changes, create related A/T events
1257 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1258 $ses->request('open-ils.trigger.event.autocreate', 'damaged', $copy, $owning_lib);
1261 $logger->debug("resetting holds that target the marked copy");
1262 OpenILS::Application::Circ::Holds->_reset_hold($e->requestor, $_) for @$holds;
1267 sub handle_mark_damaged {
1268 my($e, $copy, $owning_lib, $args) = @_;
1270 my $apply = $args->{apply_fines} || '';
1271 return undef if $apply eq 'noapply';
1273 my $new_amount = $args->{override_amount};
1274 my $new_btype = $args->{override_btype};
1275 my $new_note = $args->{override_note};
1277 # grab the last circulation
1278 my $circ = $e->search_action_circulation([
1279 { target_copy => $copy->id},
1281 order_by => {circ => "xact_start DESC"},
1283 flesh_fields => {circ => ['target_copy', 'usr'], au => ['card']}
1287 return undef unless $circ;
1289 my $charge_price = $U->ou_ancestor_setting_value(
1290 $owning_lib, 'circ.charge_on_damaged', $e);
1292 my $proc_fee = $U->ou_ancestor_setting_value(
1293 $owning_lib, 'circ.damaged_item_processing_fee', $e) || 0;
1295 my $void_overdue = $U->ou_ancestor_setting_value(
1296 $owning_lib, 'circ.damaged.void_ovedue', $e) || 0;
1298 return undef unless $charge_price or $proc_fee;
1300 my $copy_price = ($charge_price) ? $U->get_copy_price($e, $copy) : 0;
1301 my $total = $copy_price + $proc_fee;
1305 if($new_amount and $new_btype) {
1307 # Allow staff to override the amount to charge for a damaged item
1308 # Consider the case where the item is only partially damaged
1309 # This value is meant to take the place of the item price and
1310 # optional processing fee.
1312 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1313 $e, $new_amount, $new_btype, 'Damaged Item Override', $circ->id, $new_note);
1314 return $evt if $evt;
1318 if($charge_price and $copy_price) {
1319 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1320 $e, $copy_price, 7, 'Damaged Item', $circ->id);
1321 return $evt if $evt;
1325 my $evt = OpenILS::Application::Circ::CircCommon->create_bill(
1326 $e, $proc_fee, 8, 'Damaged Item Processing Fee', $circ->id);
1327 return $evt if $evt;
1331 # the assumption is that you would not void the overdues unless you
1332 # were also charging for the item and/or applying a processing fee
1334 my $evt = OpenILS::Application::Circ::CircCommon->void_or_zero_overdues($e, $circ, {note => 'System: OVERDUE REVERSED FOR DAMAGE CHARGE'});
1335 return $evt if $evt;
1338 my $evt = OpenILS::Application::Circ::CircCommon->reopen_xact($e, $circ->id);
1339 return $evt if $evt;
1341 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1342 $ses->request('open-ils.trigger.event.autocreate', 'checkout.damaged', $circ, $circ->circ_lib);
1344 my $evt2 = OpenILS::Utils::Penalty->calculate_penalties($e, $circ->usr->id, $e->requestor->ws_ou);
1345 return $evt2 if $evt2;
1350 return OpenILS::Event->new('DAMAGE_CHARGE',
1361 # ----------------------------------------------------------------------
1362 __PACKAGE__->register_method(
1363 method => 'mark_item_missing_pieces',
1364 api_name => 'open-ils.circ.mark_item_missing_pieces',
1366 Changes the status of a copy to "damaged" or to a custom status based on the
1367 circ.missing_pieces.copy_status org unit setting. Requires MARK_ITEM_MISSING_PIECES
1369 @param authtoken The login session key
1370 @param copy_id The ID of the copy to mark as damaged
1371 @return Success event with circ and copy objects in the payload, or error Event otherwise.
1375 sub mark_item_missing_pieces {
1376 my( $self, $conn, $auth, $copy_id, $args ) = @_;
1377 ### FIXME: We're starting a transaction here, but we're doing a lot of things outside of the transaction
1378 ### FIXME: Even better, we're going to use two transactions, the first to affect pertinent holds before checkout can
1380 my $e2 = new_editor(authtoken=>$auth, xact =>1);
1381 return $e2->die_event unless $e2->checkauth;
1384 my $copy = $e2->retrieve_asset_copy([
1386 {flesh => 1, flesh_fields => {'acp' => ['call_number']}}])
1387 or return $e2->die_event;
1390 ($copy->call_number->id == OILS_PRECAT_CALL_NUMBER) ?
1391 $copy->circ_lib : $copy->call_number->owning_lib;
1393 return $e2->die_event unless $e2->allowed('MARK_ITEM_MISSING_PIECES', $owning_lib);
1395 #### grab the last circulation
1396 my $circ = $e2->search_action_circulation([
1397 { target_copy => $copy->id},
1399 order_by => {circ => "xact_start DESC"}
1404 $logger->info('open-ils.circ.mark_item_missing_pieces: no previous checkout');
1406 return OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND',{'copy'=>$copy});
1409 my $holds = $e2->search_action_hold_request(
1411 current_copy => $copy->id,
1412 fulfillment_time => undef,
1413 cancel_time => undef,
1417 $logger->debug("resetting holds that target the marked copy");
1418 OpenILS::Application::Circ::Holds->_reset_hold($e2->requestor, $_) for @$holds;
1421 if (! $e2->commit) {
1422 return $e2->die_event;
1425 my $e = new_editor(authtoken=>$auth, xact =>1);
1426 return $e->die_event unless $e->checkauth;
1428 if (! $circ->checkin_time) { # if circ active, attempt renew
1429 my ($res) = $self->method_lookup('open-ils.circ.renew')->run($e->authtoken,{'copy_id'=>$circ->target_copy});
1430 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1431 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1432 $circ = $res->[0]->{payload}{'circ'};
1433 $circ->target_copy( $copy->id );
1434 $logger->info('open-ils.circ.mark_item_missing_pieces: successful renewal');
1436 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful renewal');
1441 'copy_id'=>$circ->target_copy,
1442 'patron_id'=>$circ->usr,
1443 'skip_deposit_fee'=>1,
1444 'skip_rental_fee'=>1
1447 if ($U->ou_ancestor_setting_value($e->requestor->ws_ou, 'circ.block_renews_for_holds')) {
1449 my ($hold, undef, $retarget) = $holdcode->find_nearest_permitted_hold(
1450 $e, $copy, $e->requestor, 1 );
1452 if ($hold) { # needed for hold? then due now
1454 $logger->info('open-ils.circ.mark_item_missing_pieces: item needed for hold, shortening due date');
1455 my $due_date = DateTime->now(time_zone => 'local');
1456 $co_params->{'due_date'} = cleanse_ISO8601( $due_date->strftime('%FT%T%z') );
1458 $logger->info('open-ils.circ.mark_item_missing_pieces: item not needed for hold');
1462 my ($res) = $self->method_lookup('open-ils.circ.checkout.full.override')->run($e->authtoken,$co_params,{ all => 1 });
1463 if (ref $res ne 'ARRAY') { $res = [ $res ]; }
1464 if ( $res->[0]->{textcode} eq 'SUCCESS' ) {
1465 $logger->info('open-ils.circ.mark_item_missing_pieces: successful checkout');
1466 $circ = $res->[0]->{payload}{'circ'};
1468 $logger->info('open-ils.circ.mark_item_missing_pieces: non-successful checkout');
1474 ### Update the item status
1476 my $custom_stat = $U->ou_ancestor_setting_value(
1477 $owning_lib, 'circ.missing_pieces.copy_status', $e);
1478 my $stat = $custom_stat || OILS_COPY_STATUS_DAMAGED;
1480 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1481 $ses->request('open-ils.trigger.event.autocreate', 'missing_pieces', $copy, $owning_lib);
1483 $copy->status($stat);
1484 $copy->edit_date('now');
1485 $copy->editor($e->requestor->id);
1487 $e->update_asset_copy($copy) or return $e->die_event;
1491 my $ses = OpenSRF::AppSession->create('open-ils.trigger');
1492 $ses->request('open-ils.trigger.event.autocreate', 'circ.missing_pieces', $circ, $circ->circ_lib);
1494 return OpenILS::Event->new('SUCCESS',
1498 slip => $U->fire_object_event(undef, 'circ.format.missing_pieces.slip.print', $circ, $circ->circ_lib),
1499 letter => $U->fire_object_event(undef, 'circ.format.missing_pieces.letter.print', $circ, $circ->circ_lib)
1504 return $e->die_event;
1512 # ----------------------------------------------------------------------
1513 __PACKAGE__->register_method(
1514 method => 'magic_fetch',
1515 api_name => 'open-ils.agent.fetch'
1518 my @FETCH_ALLOWED = qw/ aou aout acp acn bre /;
1521 my( $self, $conn, $auth, $args ) = @_;
1522 my $e = new_editor( authtoken => $auth );
1523 return $e->event unless $e->checkauth;
1525 my $hint = $$args{hint};
1526 my $id = $$args{id};
1528 # Is the call allowed to fetch this type of object?
1529 return undef unless grep { $_ eq $hint } @FETCH_ALLOWED;
1531 # Find the class the implements the given hint
1532 my ($class) = grep {
1533 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
1535 $class =~ s/Fieldmapper:://og;
1536 $class =~ s/::/_/og;
1537 my $method = "retrieve_$class";
1539 my $obj = $e->$method($id) or return $e->event;
1542 # ----------------------------------------------------------------------
1545 __PACKAGE__->register_method(
1546 method => "fleshed_circ_retrieve",
1548 api_name => "open-ils.circ.fleshed.retrieve",);
1550 sub fleshed_circ_retrieve {
1551 my( $self, $client, $id ) = @_;
1552 my $e = new_editor();
1553 my $circ = $e->retrieve_action_circulation(
1559 circ => [ qw/ target_copy / ],
1560 acp => [ qw/ location status stat_cat_entry_copy_maps notes age_protect call_number parts / ],
1561 ascecm => [ qw/ stat_cat stat_cat_entry / ],
1562 acn => [ qw/ record / ],
1566 ) or return $e->event;
1568 my $copy = $circ->target_copy;
1569 my $vol = $copy->call_number;
1570 my $rec = $circ->target_copy->call_number->record;
1572 $vol->record($rec->id);
1573 $copy->call_number($vol->id);
1574 $circ->target_copy($copy->id);
1578 if( $rec->id == OILS_PRECAT_RECORD ) {
1582 $mvr = $U->record_to_mvr($rec);
1583 $rec->marc(''); # drop the bulky marc data
1597 __PACKAGE__->register_method(
1598 method => "test_batch_circ_events",
1599 api_name => "open-ils.circ.trigger_event_by_def_and_barcode.fire"
1602 # method for testing the behavior of a given event definition
1603 sub test_batch_circ_events {
1604 my($self, $conn, $auth, $event_def, $barcode) = @_;
1606 my $e = new_editor(authtoken => $auth);
1607 return $e->event unless $e->checkauth;
1608 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1610 my $copy = $e->search_asset_copy({barcode => $barcode, deleted => 'f'})->[0]
1611 or return $e->event;
1613 my $circ = $e->search_action_circulation(
1614 {target_copy => $copy->id, checkin_time => undef})->[0]
1615 or return $e->event;
1617 return undef unless $circ;
1619 return $U->fire_object_event($event_def, undef, $circ, $e->requestor->ws_ou)
1623 __PACKAGE__->register_method(
1624 method => "fire_circ_events",
1625 api_name => "open-ils.circ.fire_circ_trigger_events",
1627 General event def runner for circ objects. If no event def ID
1628 is provided, the hook will be used to find the best event_def
1629 match based on the context org unit
1633 __PACKAGE__->register_method(
1634 method => "fire_circ_events",
1635 api_name => "open-ils.circ.fire_hold_trigger_events",
1637 General event def runner for hold objects. If no event def ID
1638 is provided, the hook will be used to find the best event_def
1639 match based on the context org unit
1643 __PACKAGE__->register_method(
1644 method => "fire_circ_events",
1645 api_name => "open-ils.circ.fire_user_trigger_events",
1647 General event def runner for user objects. If no event def ID
1648 is provided, the hook will be used to find the best event_def
1649 match based on the context org unit
1654 sub fire_circ_events {
1655 my($self, $conn, $auth, $org_id, $event_def, $hook, $granularity, $target_ids, $user_data) = @_;
1657 my $e = new_editor(authtoken => $auth, xact => 1);
1658 return $e->event unless $e->checkauth;
1662 if($self->api_name =~ /hold/) {
1663 return $e->event unless $e->allowed('VIEW_HOLD', $org_id);
1664 $targets = $e->batch_retrieve_action_hold_request($target_ids);
1665 } elsif($self->api_name =~ /user/) {
1666 return $e->event unless $e->allowed('VIEW_USER', $org_id);
1667 $targets = $e->batch_retrieve_actor_user($target_ids);
1669 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $org_id);
1670 $targets = $e->batch_retrieve_action_circulation($target_ids);
1672 $e->rollback; # FIXME using transaction because of pgpool/slony setups, but not
1673 # simply making this method authoritative because of weirdness
1674 # with transaction handling in A/T code that causes rollback
1675 # failure down the line if handling many targets
1677 return undef unless @$targets;
1678 return $U->fire_object_event($event_def, $hook, $targets, $org_id, $granularity, $user_data);
1681 __PACKAGE__->register_method(
1682 method => "user_payments_list",
1683 api_name => "open-ils.circ.user_payments.filtered.batch",
1686 desc => q/Returns a fleshed, date-limited set of all payments a user
1687 has made. By default, ordered by payment date. Optionally
1688 ordered by other columns in the top-level "mp" object/,
1690 {desc => 'Authentication token', type => 'string'},
1691 {desc => 'User ID', type => 'number'},
1692 {desc => 'Order by column(s), optional. Array of "mp" class columns', type => 'array'}
1694 return => {desc => q/List of "mp" objects, fleshed with the billable transaction
1695 and the related fully-realized payment object (e.g money.cash_payment)/}
1699 sub user_payments_list {
1700 my($self, $conn, $auth, $user_id, $start_date, $end_date, $order_by) = @_;
1702 my $e = new_editor(authtoken => $auth);
1703 return $e->event unless $e->checkauth;
1705 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1706 return $e->event unless $e->allowed('VIEW_CIRCULATIONS', $user->home_ou);
1708 $order_by ||= ['payment_ts'];
1710 # all payments by user, between start_date and end_date
1711 my $payments = $e->json_query({
1712 select => {mp => ['id']},
1716 fkey => 'xact', field => 'id'}
1720 '+mbt' => {usr => $user_id},
1721 '+mp' => {payment_ts => {between => [$start_date, $end_date]}}
1723 order_by => {mp => $order_by}
1726 for my $payment_id (@$payments) {
1727 my $payment = $e->retrieve_money_payment([
1735 'credit_card_payment',
1750 $conn->respond($payment);
1757 __PACKAGE__->register_method(
1758 method => "retrieve_circ_chain",
1759 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ",
1762 desc => q/Given a circulation, this returns all circulation objects
1763 that are part of the same chain of renewals./,
1765 {desc => 'Authentication token', type => 'string'},
1766 {desc => 'Circ ID', type => 'number'},
1768 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1772 __PACKAGE__->register_method(
1773 method => "retrieve_circ_chain",
1774 api_name => "open-ils.circ.renewal_chain.retrieve_by_circ.summary",
1776 desc => q/Given a circulation, this returns a summary of the circulation objects
1777 that are part of the same chain of renewals./,
1779 {desc => 'Authentication token', type => 'string'},
1780 {desc => 'Circ ID', type => 'number'},
1782 return => {desc => q/Circulation Chain Summary/}
1786 sub retrieve_circ_chain {
1787 my($self, $conn, $auth, $circ_id) = @_;
1789 my $e = new_editor(authtoken => $auth);
1790 return $e->event unless $e->checkauth;
1791 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1793 if($self->api_name =~ /summary/) {
1794 return $U->create_circ_chain_summary($e, $circ_id);
1798 my $chain = $e->json_query({from => ['action.circ_chain', $circ_id]});
1800 for my $circ_info (@$chain) {
1801 my $circ = Fieldmapper::action::circulation->new;
1802 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1803 $conn->respond($circ);
1810 __PACKAGE__->register_method(
1811 method => "retrieve_prev_circ_chain",
1812 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ",
1815 desc => q/Given a circulation, this returns all circulation objects
1816 that are part of the previous chain of renewals./,
1818 {desc => 'Authentication token', type => 'string'},
1819 {desc => 'Circ ID', type => 'number'},
1821 return => {desc => q/List of circ objects, orderd by oldest circ first/}
1825 __PACKAGE__->register_method(
1826 method => "retrieve_prev_circ_chain",
1827 api_name => "open-ils.circ.prev_renewal_chain.retrieve_by_circ.summary",
1829 desc => q/Given a circulation, this returns a summary of the circulation objects
1830 that are part of the previous chain of renewals./,
1832 {desc => 'Authentication token', type => 'string'},
1833 {desc => 'Circ ID', type => 'number'},
1835 return => {desc => q/Object containing Circulation Chain Summary and User Id/}
1839 sub retrieve_prev_circ_chain {
1840 my($self, $conn, $auth, $circ_id) = @_;
1842 my $e = new_editor(authtoken => $auth);
1843 return $e->event unless $e->checkauth;
1844 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1846 if($self->api_name =~ /summary/) {
1847 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1848 my $target_copy = $$first_circ{'target_copy'};
1849 my $usr = $$first_circ{'usr'};
1850 my $last_circ_from_prev_chain = $e->json_query({
1851 'select' => { 'circ' => ['id','usr'] },
1854 target_copy => $target_copy,
1855 xact_start => { '<' => $$first_circ{'xact_start'} }
1857 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1860 return undef unless $last_circ_from_prev_chain;
1861 return undef unless $$last_circ_from_prev_chain{'id'};
1862 my $sum = $e->json_query({from => ['action.summarize_circ_chain', $$last_circ_from_prev_chain{'id'}]})->[0];
1863 return undef unless $sum;
1864 my $obj = Fieldmapper::action::circ_chain_summary->new;
1865 $obj->$_($sum->{$_}) for keys %$sum;
1866 return { 'summary' => $obj, 'usr' => $$last_circ_from_prev_chain{'usr'} };
1870 my $first_circ = $e->json_query({from => ['action.circ_chain', $circ_id]})->[0];
1871 my $target_copy = $$first_circ{'target_copy'};
1872 my $last_circ_from_prev_chain = $e->json_query({
1873 'select' => { 'circ' => ['id'] },
1876 target_copy => $target_copy,
1877 xact_start => { '<' => $$first_circ{'xact_start'} }
1879 'order_by' => [{ 'class'=>'circ', 'field'=>'xact_start', 'direction'=>'desc' }],
1882 return undef unless $last_circ_from_prev_chain;
1883 return undef unless $$last_circ_from_prev_chain{'id'};
1884 my $chain = $e->json_query({from => ['action.circ_chain', $$last_circ_from_prev_chain{'id'}]});
1886 for my $circ_info (@$chain) {
1887 my $circ = Fieldmapper::action::circulation->new;
1888 $circ->$_($circ_info->{$_}) for keys %$circ_info;
1889 $conn->respond($circ);
1897 __PACKAGE__->register_method(
1898 method => "get_copy_due_date",
1899 api_name => "open-ils.circ.copy.due_date.retrieve",
1902 Given a copy ID, returns the due date for the copy if it's
1903 currently circulating. Otherwise, returns null. Note, this is a public
1904 method requiring no authentication. Only the due date is exposed.
1907 {desc => 'Copy ID', type => 'number'}
1909 return => {desc => q/
1910 Due date (ISO date stamp) if the copy is circulating, null otherwise.
1915 sub get_copy_due_date {
1916 my($self, $conn, $copy_id) = @_;
1917 my $e = new_editor();
1919 my $circ = $e->json_query({
1920 select => {circ => ['due_date']},
1923 target_copy => $copy_id,
1924 checkin_time => undef,
1926 {stop_fines => ["MAXFINES","LONGOVERDUE"]},
1927 {stop_fines => undef}
1931 })->[0] or return undef;
1933 return $circ->{due_date};
1940 # {"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}}