1 package OpenILS::Application::AppUtils;
3 use strict; use warnings;
4 use OpenILS::Application;
5 use base qw/OpenILS::Application/;
6 use OpenSRF::Utils::Cache;
7 use OpenSRF::Utils::Logger qw/$logger/;
8 use OpenILS::Utils::ModsParser;
9 use OpenSRF::EX qw(:try);
12 use OpenILS::Utils::CStoreEditor;
13 use OpenILS::Const qw/:const/;
14 use Unicode::Normalize;
15 use OpenSRF::Utils::SettingsClient;
17 # ---------------------------------------------------------------------------
18 # Pile of utilty methods used accross applications.
19 # ---------------------------------------------------------------------------
20 my $cache_client = "OpenSRF::Utils::Cache";
23 # ---------------------------------------------------------------------------
24 # on sucess, returns the created session, on failure throws ERROR exception
25 # ---------------------------------------------------------------------------
26 sub start_db_session {
29 my $session = OpenSRF::AppSession->connect( "open-ils.storage" );
30 my $trans_req = $session->request( "open-ils.storage.transaction.begin" );
32 my $trans_resp = $trans_req->recv();
33 if(ref($trans_resp) and UNIVERSAL::isa($trans_resp,"Error")) { throw $trans_resp; }
34 if( ! $trans_resp->content() ) {
36 ("Unable to Begin Transaction with database" );
40 $logger->debug("Setting global storage session to ".
41 "session: " . $session->session_id . " : " . $session->app );
49 transform => 'permission.usr_has_perm',
60 # returns undef if user has all of the perms provided
61 # returns the first failed perm on failure
62 sub check_user_perms {
63 my($self, $user_id, $org_id, @perm_types ) = @_;
64 $logger->debug("Checking perms with user : $user_id , org: $org_id, @perm_types");
66 for my $type (@perm_types) {
67 $PERM_QUERY->{select}->{au}->[0]->{params} = [$type, $org_id];
68 $PERM_QUERY->{where}->{id} = $user_id;
69 return $type unless $self->is_true(OpenILS::Utils::CStoreEditor->new->json_query($PERM_QUERY)->[0]->{has_perm});
74 # checks the list of user perms. The first one that fails returns a new
76 my( $self, $user_id, $org_id, @perm_types ) = @_;
77 my $t = $self->check_user_perms( $user_id, $org_id, @perm_types );
78 return OpenILS::Event->new('PERM_FAILURE', ilsperm => $t, ilspermloc => $org_id ) if $t;
84 # ---------------------------------------------------------------------------
85 # commits and destroys the session
86 # ---------------------------------------------------------------------------
87 sub commit_db_session {
88 my( $self, $session ) = @_;
90 my $req = $session->request( "open-ils.storage.transaction.commit" );
91 my $resp = $req->recv();
94 throw OpenSRF::EX::ERROR ("Unable to commit db session");
97 if(UNIVERSAL::isa($resp,"Error")) {
98 throw $resp ($resp->stringify);
101 if(!$resp->content) {
102 throw OpenSRF::EX::ERROR ("Unable to commit db session");
106 $session->disconnect();
110 sub rollback_db_session {
111 my( $self, $session ) = @_;
113 my $req = $session->request("open-ils.storage.transaction.rollback");
114 my $resp = $req->recv();
115 if(UNIVERSAL::isa($resp,"Error")) { throw $resp; }
118 $session->disconnect();
123 # returns undef it the event is not an ILS event
124 # returns the event code otherwise
126 my( $self, $evt ) = @_;
127 return $evt->{ilsevent} if( ref($evt) eq 'HASH' and defined($evt->{ilsevent})) ;
131 # ---------------------------------------------------------------------------
132 # Checks to see if a user is logged in. Returns the user record on success,
133 # throws an exception on error.
134 # ---------------------------------------------------------------------------
135 sub check_user_session {
137 my( $self, $user_session ) = @_;
139 my $content = $self->simplereq(
141 'open-ils.auth.session.retrieve', $user_session );
143 if(! $content or $self->event_code($content)) {
144 throw OpenSRF::EX::ERROR
145 ("Session [$user_session] cannot be authenticated" );
148 $logger->debug("Fetch user session $user_session found user " . $content->id );
153 # generic simple request returning a scalar value
155 my($self, $service, $method, @params) = @_;
156 return $self->simple_scalar_request($service, $method, @params);
160 sub simple_scalar_request {
161 my($self, $service, $method, @params) = @_;
163 my $session = OpenSRF::AppSession->create( $service );
165 my $request = $session->request( $method, @params );
171 $val = $request->gather(1);
178 warn "received error : service=$service : method=$method : params=".Dumper(\@params) . "\n $err";
179 throw $err ("Call to $service for method $method \n failed with exception: $err : " );
191 my $org_typelist = undef;
192 my $org_typelist_hash = {};
196 # can we throw this version away??
199 if($tree) { return $tree; }
201 # see if it's in the cache
202 $tree = $cache_client->new()->get_cache('_orgtree');
203 if($tree) { return $tree; }
206 warn "Retrieving Org Tree\n";
207 $orglist = $self->simple_scalar_request(
209 "open-ils.cstore.direct.actor.org_unit.search.atomic",
210 { id => { '!=' => undef } }
214 if( ! $org_typelist ) {
215 warn "Retrieving org types\n";
216 $org_typelist = $self->simple_scalar_request(
218 "open-ils.cstore.direct.actor.org_unit_type.search.atomic",
219 { id => { '!=' => undef } }
221 $self->build_org_type($org_typelist);
224 $tree = $self->build_org_tree($orglist,1);
225 $cache_client->new()->put_cache('_orgtree', $tree);
230 my $slimtree = undef;
231 sub get_slim_org_tree {
234 if($slimtree) { return $slimtree; }
236 # see if it's in the cache
237 $slimtree = $cache_client->new()->get_cache('slimorgtree');
238 if($slimtree) { return $slimtree; }
241 warn "Retrieving Org Tree\n";
242 $orglist = $self->simple_scalar_request(
244 "open-ils.cstore.direct.actor.org_unit.search.atomic",
245 { id => { '!=' => undef } }
249 $slimtree = $self->build_org_tree($orglist);
250 $cache_client->new->put_cache('slimorgtree', $slimtree);
257 my($self, $org_typelist) = @_;
258 for my $type (@$org_typelist) {
259 $org_typelist_hash->{$type->id()} = $type;
267 my( $self, $orglist, $add_types ) = @_;
269 return $orglist unless ref $orglist;
270 return $$orglist[0] if @$orglist == 1;
273 $a->ou_type <=> $b->ou_type ||
274 $a->name cmp $b->name } @$orglist;
276 for my $org (@list) {
280 if(!ref($org->ou_type()) and $add_types) {
281 $org->ou_type( $org_typelist_hash->{$org->ou_type()});
284 next unless (defined($org->parent_ou));
286 my ($parent) = grep { $_->id == $org->parent_ou } @list;
288 $parent->children([]) unless defined($parent->children);
289 push( @{$parent->children}, $org );
295 sub fetch_closed_date {
296 my( $self, $cd ) = @_;
299 $logger->debug("Fetching closed_date $cd from cstore");
301 my $cd_obj = $self->simplereq(
303 'open-ils.cstore.direct.actor.org_unit.closed_date.retrieve', $cd );
306 $logger->info("closed_date $cd not found in the db");
307 $evt = OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
310 return ($cd_obj, $evt);
314 my( $self, $userid ) = @_;
317 $logger->debug("Fetching user $userid from cstore");
319 $user = $self->simplereq(
321 'open-ils.cstore.direct.actor.user.retrieve', $userid );
324 $logger->info("User $userid not found in the db");
325 $evt = OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
328 return ($user, $evt);
332 my( $self, $session ) = @_;
333 my $user; my $evt; my $e;
335 $logger->debug("Checking user session $session");
338 $user = $self->check_user_session($session);
339 } catch Error with { $e = 1; };
341 $logger->debug("Done checking user session $session " . (($e) ? "error = $e" : "") );
343 if( $e or !$user ) { $evt = OpenILS::Event->new('NO_SESSION'); }
344 return ( $user, $evt );
348 # verifiese the session and checks the permissions agains the
349 # session user and the user's home_ou as the org id
351 my( $self, $session, @perms ) = @_;
352 my $user; my $evt; my $e;
353 $logger->debug("Checking user session $session and perms @perms");
354 ($user, $evt) = $self->checkses($session);
355 return (undef, $evt) if $evt;
356 $evt = $self->check_perms($user->id, $user->home_ou, @perms);
357 return ($user, $evt);
362 my( $self, $staffobj, $userid, @perms ) = @_;
364 $userid = $staffobj->id unless defined $userid;
366 $logger->debug("checkrequestor(): requestor => " . $staffobj->id . ", target => $userid");
368 if( $userid ne $staffobj->id ) {
369 ($user, $evt) = $self->fetch_user($userid);
370 return (undef, $evt) if $evt;
371 $evt = $self->check_perms( $staffobj->id, $user->home_ou, @perms );
377 return ($user, $evt);
380 sub checkses_requestor {
381 my( $self, $authtoken, $targetid, @perms ) = @_;
382 my( $requestor, $target, $evt );
384 ($requestor, $evt) = $self->checkses($authtoken);
385 return (undef, undef, $evt) if $evt;
387 ($target, $evt) = $self->checkrequestor( $requestor, $targetid, @perms );
388 return( $requestor, $target, $evt);
392 my( $self, $copyid ) = @_;
395 $logger->debug("Fetching copy $copyid from cstore");
397 $copy = $self->simplereq(
399 'open-ils.cstore.direct.asset.copy.retrieve', $copyid );
401 if(!$copy) { $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND'); }
403 return( $copy, $evt );
407 # retrieves a circ object by id
408 sub fetch_circulation {
409 my( $self, $circid ) = @_;
412 $logger->debug("Fetching circ $circid from cstore");
414 $circ = $self->simplereq(
416 "open-ils.cstore.direct.action.circulation.retrieve", $circid );
419 $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND', circid => $circid );
422 return ( $circ, $evt );
425 sub fetch_record_by_copy {
426 my( $self, $copyid ) = @_;
429 $logger->debug("Fetching record by copy $copyid from cstore");
431 $record = $self->simplereq(
433 'open-ils.cstore.direct.asset.copy.retrieve', $copyid,
435 flesh_fields => { bre => [ 'fixed_fields' ],
437 acp => [ 'call_number' ],
443 $evt = OpenILS::Event->new('BIBLIO_RECORD_ENTRY_NOT_FOUND');
445 $record = $record->call_number->record;
448 return ($record, $evt);
451 # turns a record object into an mvr (mods) object
453 my( $self, $record ) = @_;
454 return undef unless $record and $record->marc;
455 my $u = OpenILS::Utils::ModsParser->new();
456 $u->start_mods_batch( $record->marc );
457 my $mods = $u->finish_mods_batch();
458 $mods->doc_id($record->id);
459 $mods->tcn($record->tcn_value);
464 my( $self, $holdid ) = @_;
467 $logger->debug("Fetching hold $holdid from cstore");
469 $hold = $self->simplereq(
471 'open-ils.cstore.direct.action.hold_request.retrieve', $holdid);
473 $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND', holdid => $holdid) unless $hold;
475 return ($hold, $evt);
479 sub fetch_hold_transit_by_hold {
480 my( $self, $holdid ) = @_;
481 my( $transit, $evt );
483 $logger->debug("Fetching transit by hold $holdid from cstore");
485 $transit = $self->simplereq(
487 'open-ils.cstore.direct.action.hold_transit_copy.search', { hold => $holdid } );
489 $evt = OpenILS::Event->new('ACTION_HOLD_TRANSIT_COPY_NOT_FOUND', holdid => $holdid) unless $transit;
491 return ($transit, $evt );
494 # fetches the captured, but not fulfilled hold attached to a given copy
495 sub fetch_open_hold_by_copy {
496 my( $self, $copyid ) = @_;
497 $logger->debug("Searching for active hold for copy $copyid");
500 $hold = $self->cstorereq(
501 'open-ils.cstore.direct.action.hold_request.search',
503 current_copy => $copyid ,
504 capture_time => { "!=" => undef },
505 fulfillment_time => undef,
506 cancel_time => undef,
509 $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND', copyid => $copyid) unless $hold;
510 return ($hold, $evt);
513 sub fetch_hold_transit {
514 my( $self, $transid ) = @_;
515 my( $htransit, $evt );
516 $logger->debug("Fetching hold transit with hold id $transid");
517 $htransit = $self->cstorereq(
518 'open-ils.cstore.direct.action.hold_transit_copy.retrieve', $transid );
519 $evt = OpenILS::Event->new('ACTION_HOLD_TRANSIT_COPY_NOT_FOUND', id => $transid) unless $htransit;
520 return ($htransit, $evt);
523 sub fetch_copy_by_barcode {
524 my( $self, $barcode ) = @_;
527 $logger->debug("Fetching copy by barcode $barcode from cstore");
529 $copy = $self->simplereq( 'open-ils.cstore',
530 'open-ils.cstore.direct.asset.copy.search', { barcode => $barcode, deleted => 'f'} );
531 #'open-ils.storage.direct.asset.copy.search.barcode', $barcode );
533 $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND', barcode => $barcode) unless $copy;
535 return ($copy, $evt);
538 sub fetch_open_billable_transaction {
539 my( $self, $transid ) = @_;
540 my( $transaction, $evt );
542 $logger->debug("Fetching open billable transaction $transid from cstore");
544 $transaction = $self->simplereq(
546 'open-ils.cstore.direct.money.open_billable_transaction_summary.retrieve', $transid);
548 $evt = OpenILS::Event->new(
549 'MONEY_OPEN_BILLABLE_TRANSACTION_SUMMARY_NOT_FOUND', transid => $transid ) unless $transaction;
551 return ($transaction, $evt);
557 $buckets{'biblio'} = 'biblio_record_entry_bucket';
558 $buckets{'callnumber'} = 'call_number_bucket';
559 $buckets{'copy'} = 'copy_bucket';
560 $buckets{'user'} = 'user_bucket';
562 sub fetch_container {
563 my( $self, $id, $type ) = @_;
566 $logger->debug("Fetching container $id with type $type");
568 my $e = 'CONTAINER_CALL_NUMBER_BUCKET_NOT_FOUND';
569 $e = 'CONTAINER_BIBLIO_RECORD_ENTRY_BUCKET_NOT_FOUND' if $type eq 'biblio';
570 $e = 'CONTAINER_USER_BUCKET_NOT_FOUND' if $type eq 'user';
571 $e = 'CONTAINER_COPY_BUCKET_NOT_FOUND' if $type eq 'copy';
573 my $meth = $buckets{$type};
574 $bucket = $self->simplereq(
576 "open-ils.cstore.direct.container.$meth.retrieve", $id );
578 $evt = OpenILS::Event->new(
579 $e, container => $id, container_type => $type ) unless $bucket;
581 return ($bucket, $evt);
585 sub fetch_container_e {
586 my( $self, $editor, $id, $type ) = @_;
589 $bucket = $editor->retrieve_container_copy_bucket($id) if $type eq 'copy';
590 $bucket = $editor->retrieve_container_call_number_bucket($id) if $type eq 'callnumber';
591 $bucket = $editor->retrieve_container_biblio_record_entry_bucket($id) if $type eq 'biblio';
592 $bucket = $editor->retrieve_container_user_bucket($id) if $type eq 'user';
594 $evt = $editor->event unless $bucket;
595 return ($bucket, $evt);
598 sub fetch_container_item_e {
599 my( $self, $editor, $id, $type ) = @_;
602 $bucket = $editor->retrieve_container_copy_bucket_item($id) if $type eq 'copy';
603 $bucket = $editor->retrieve_container_call_number_bucket_item($id) if $type eq 'callnumber';
604 $bucket = $editor->retrieve_container_biblio_record_entry_bucket_item($id) if $type eq 'biblio';
605 $bucket = $editor->retrieve_container_user_bucket_item($id) if $type eq 'user';
607 $evt = $editor->event unless $bucket;
608 return ($bucket, $evt);
615 sub fetch_container_item {
616 my( $self, $id, $type ) = @_;
619 $logger->debug("Fetching container item $id with type $type");
621 my $meth = $buckets{$type} . "_item";
623 $bucket = $self->simplereq(
625 "open-ils.cstore.direct.container.$meth.retrieve", $id );
628 my $e = 'CONTAINER_CALL_NUMBER_BUCKET_ITEM_NOT_FOUND';
629 $e = 'CONTAINER_BIBLIO_RECORD_ENTRY_BUCKET_ITEM_NOT_FOUND' if $type eq 'biblio';
630 $e = 'CONTAINER_USER_BUCKET_ITEM_NOT_FOUND' if $type eq 'user';
631 $e = 'CONTAINER_COPY_BUCKET_ITEM_NOT_FOUND' if $type eq 'copy';
633 $evt = OpenILS::Event->new(
634 $e, itemid => $id, container_type => $type ) unless $bucket;
636 return ($bucket, $evt);
640 sub fetch_patron_standings {
642 $logger->debug("Fetching patron standings");
643 return $self->simplereq(
645 'open-ils.cstore.direct.config.standing.search.atomic', { id => { '!=' => undef } });
649 sub fetch_permission_group_tree {
651 $logger->debug("Fetching patron profiles");
652 return $self->simplereq(
654 'open-ils.actor.groups.tree.retrieve' );
658 sub fetch_patron_circ_summary {
659 my( $self, $userid ) = @_;
660 $logger->debug("Fetching patron summary for $userid");
661 my $summary = $self->simplereq(
663 "open-ils.storage.action.circulation.patron_summary", $userid );
667 $summary->[1] ||= 0.0;
674 sub fetch_copy_statuses {
676 $logger->debug("Fetching copy statuses");
677 return $self->simplereq(
679 'open-ils.cstore.direct.config.copy_status.search.atomic', { id => { '!=' => undef } });
682 sub fetch_copy_location {
683 my( $self, $id ) = @_;
685 my $cl = $self->cstorereq(
686 'open-ils.cstore.direct.asset.copy_location.retrieve', $id );
687 $evt = OpenILS::Event->new('ASSET_COPY_LOCATION_NOT_FOUND') unless $cl;
691 sub fetch_copy_locations {
693 return $self->simplereq(
695 'open-ils.cstore.direct.asset.copy_location.search.atomic', { id => { '!=' => undef } });
698 sub fetch_copy_location_by_name {
699 my( $self, $name, $org ) = @_;
701 my $cl = $self->cstorereq(
702 'open-ils.cstore.direct.asset.copy_location.search',
703 { name => $name, owning_lib => $org } );
704 $evt = OpenILS::Event->new('ASSET_COPY_LOCATION_NOT_FOUND') unless $cl;
708 sub fetch_callnumber {
709 my( $self, $id ) = @_;
712 my $e = OpenILS::Event->new( 'ASSET_CALL_NUMBER_NOT_FOUND', id => $id );
713 return( undef, $e ) unless $id;
715 $logger->debug("Fetching callnumber $id");
717 my $cn = $self->simplereq(
719 'open-ils.cstore.direct.asset.call_number.retrieve', $id );
720 $evt = $e unless $cn;
722 return ( $cn, $evt );
725 my %ORG_CACHE; # - these rarely change, so cache them..
727 my( $self, $id ) = @_;
728 return undef unless $id;
729 return $id if( ref($id) eq 'Fieldmapper::actor::org_unit' );
730 return $ORG_CACHE{$id} if $ORG_CACHE{$id};
731 $logger->debug("Fetching org unit $id");
734 my $org = $self->simplereq(
736 'open-ils.cstore.direct.actor.org_unit.retrieve', $id );
737 $evt = OpenILS::Event->new( 'ACTOR_ORG_UNIT_NOT_FOUND', id => $id ) unless $org;
738 $ORG_CACHE{$id} = $org;
744 my( $self, $type, $id ) = @_;
746 $logger->debug("Fetching $type stat cat: $id");
747 $cat = $self->simplereq(
749 "open-ils.cstore.direct.$type.stat_cat.retrieve", $id );
751 my $e = 'ASSET_STAT_CAT_NOT_FOUND';
752 $e = 'ACTOR_STAT_CAT_NOT_FOUND' if $type eq 'actor';
754 $evt = OpenILS::Event->new( $e, id => $id ) unless $cat;
755 return ( $cat, $evt );
758 sub fetch_stat_cat_entry {
759 my( $self, $type, $id ) = @_;
761 $logger->debug("Fetching $type stat cat entry: $id");
762 $entry = $self->simplereq(
764 "open-ils.cstore.direct.$type.stat_cat_entry.retrieve", $id );
766 my $e = 'ASSET_STAT_CAT_ENTRY_NOT_FOUND';
767 $e = 'ACTOR_STAT_CAT_ENTRY_NOT_FOUND' if $type eq 'actor';
769 $evt = OpenILS::Event->new( $e, id => $id ) unless $entry;
770 return ( $entry, $evt );
775 my( $self, $org_tree, $orgid ) = @_;
777 $logger->warn("find_org() did not receive a value for \$org_tree");
780 $logger->warn("find_org() did not receive a value for \$orgid");
783 return $org_tree if ( $org_tree->id eq $orgid );
784 return undef unless ref($org_tree->children);
785 for my $c (@{$org_tree->children}) {
786 my $o = $self->find_org($c, $orgid);
792 sub fetch_non_cat_type_by_name_and_org {
793 my( $self, $name, $orgId ) = @_;
794 $logger->debug("Fetching non cat type $name at org $orgId");
795 my $types = $self->simplereq(
797 'open-ils.cstore.direct.config.non_cataloged_type.search.atomic',
798 { name => $name, owning_lib => $orgId } );
799 return ($types->[0], undef) if($types and @$types);
800 return (undef, OpenILS::Event->new('CONFIG_NON_CATALOGED_TYPE_NOT_FOUND') );
803 sub fetch_non_cat_type {
804 my( $self, $id ) = @_;
805 $logger->debug("Fetching non cat type $id");
807 $type = $self->simplereq(
809 'open-ils.cstore.direct.config.non_cataloged_type.retrieve', $id );
810 $evt = OpenILS::Event->new('CONFIG_NON_CATALOGED_TYPE_NOT_FOUND') unless $type;
811 return ($type, $evt);
814 sub DB_UPDATE_FAILED {
815 my( $self, $payload ) = @_;
816 return OpenILS::Event->new('DATABASE_UPDATE_FAILED',
817 payload => ($payload) ? $payload : undef );
820 sub fetch_circ_duration_by_name {
821 my( $self, $name ) = @_;
823 $dur = $self->simplereq(
825 'open-ils.cstore.direct.config.rules.circ_duration.search.atomic', { name => $name } );
827 $evt = OpenILS::Event->new('CONFIG_RULES_CIRC_DURATION_NOT_FOUND') unless $dur;
831 sub fetch_recurring_fine_by_name {
832 my( $self, $name ) = @_;
834 $obj = $self->simplereq(
836 'open-ils.cstore.direct.config.rules.recuring_fine.search.atomic', { name => $name } );
838 $evt = OpenILS::Event->new('CONFIG_RULES_RECURING_FINE_NOT_FOUND') unless $obj;
842 sub fetch_max_fine_by_name {
843 my( $self, $name ) = @_;
845 $obj = $self->simplereq(
847 'open-ils.cstore.direct.config.rules.max_fine.search.atomic', { name => $name } );
849 $evt = OpenILS::Event->new('CONFIG_RULES_MAX_FINE_NOT_FOUND') unless $obj;
854 my( $self, $method, @params ) = @_;
855 return $self->simplereq(
856 'open-ils.storage', $method, @params );
859 sub storagereq_xact {
860 my($self, $method, @params) = @_;
861 my $ses = $self->start_db_session();
862 my $val = $ses->request($method, @params)->gather(1);
863 $self->rollback_db_session($ses);
868 my( $self, $method, @params ) = @_;
869 return $self->simplereq(
870 'open-ils.cstore', $method, @params );
874 my( $self, $e, $name ) = @_;
875 if( $e and ref($e) eq 'HASH' and
876 defined($e->{textcode}) and $e->{textcode} eq $name ) {
883 my( undef, $f, $l ) = caller(0);
884 my( undef, undef, undef, $s ) = caller(1);
887 $logger->debug("LOGMARK: $f:$l:$s");
891 sub fetch_open_circulation {
892 my( $self, $cid ) = @_;
895 my $circ = $self->cstorereq(
896 'open-ils.cstore.direct.action.open_circulation.search',
897 { target_copy => $cid, stop_fines_time => undef } );
898 $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND') unless $circ;
899 return ($circ, $evt);
902 sub fetch_all_open_circulation {
903 my( $self, $cid ) = @_;
906 my $circ = $self->cstorereq(
907 'open-ils.cstore.direct.action.open_circulation.search',
908 { target_copy => $cid, xact_finish => undef } );
909 $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND') unless $circ;
910 return ($circ, $evt);
914 sub copy_status_from_name {
915 my( $self, $name ) = @_;
916 $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
917 for my $status (@$copy_statuses) {
918 return $status if( $status->name =~ /$name/i );
923 sub copy_status_to_name {
924 my( $self, $sid ) = @_;
925 $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
926 for my $status (@$copy_statuses) {
927 return $status->name if( $status->id == $sid );
934 my( $self, $arg ) = @_;
935 return $arg if ref $arg;
936 $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
937 my ($stat) = grep { $_->id == $arg } @$copy_statuses;
941 sub fetch_open_transit_by_copy {
942 my( $self, $copyid ) = @_;
944 $transit = $self->cstorereq(
945 'open-ils.cstore.direct.action.transit_copy.search',
946 { target_copy => $copyid, dest_recv_time => undef });
947 $evt = OpenILS::Event->new('ACTION_TRANSIT_COPY_NOT_FOUND') unless $transit;
948 return ($transit, $evt);
952 my( $self, $copy ) = @_;
953 return undef unless $copy;
954 $copy->status( $copy->status->id ) if ref($copy->status);
955 $copy->location( $copy->location->id ) if ref($copy->location);
956 $copy->circ_lib( $copy->circ_lib->id ) if ref($copy->circ_lib);
960 # un-fleshes a copy and updates it in the DB
961 # returns a DB_UPDATE_FAILED event on error
962 # returns undef on success
964 my( $self, %params ) = @_;
966 my $copy = $params{copy} || die "update_copy(): copy required";
967 my $editor = $params{editor} || die "update_copy(): copy editor required";
968 my $session = $params{session};
970 $logger->debug("Updating copy in the database: " . $copy->id);
972 $self->unflesh_copy($copy);
973 $copy->editor( $editor );
974 $copy->edit_date( 'now' );
977 my $meth = 'open-ils.storage.direct.asset.copy.update';
979 $s = $session->request( $meth, $copy )->gather(1) if $session;
980 $s = $self->storagereq( $meth, $copy ) unless $session;
982 $logger->debug("Update of copy ".$copy->id." returned: $s");
984 return $self->DB_UPDATE_FAILED($copy) unless $s;
988 sub fetch_billable_xact {
989 my( $self, $id ) = @_;
991 $logger->debug("Fetching billable transaction %id");
992 $xact = $self->cstorereq(
993 'open-ils.cstore.direct.money.billable_transaction.retrieve', $id );
994 $evt = OpenILS::Event->new('MONEY_BILLABLE_TRANSACTION_NOT_FOUND') unless $xact;
995 return ($xact, $evt);
998 sub fetch_billable_xact_summary {
999 my( $self, $id ) = @_;
1001 $logger->debug("Fetching billable transaction summary %id");
1002 $xact = $self->cstorereq(
1003 'open-ils.cstore.direct.money.billable_transaction_summary.retrieve', $id );
1004 $evt = OpenILS::Event->new('MONEY_BILLABLE_TRANSACTION_NOT_FOUND') unless $xact;
1005 return ($xact, $evt);
1008 sub fetch_fleshed_copy {
1009 my( $self, $id ) = @_;
1011 $logger->info("Fetching fleshed copy $id");
1012 $copy = $self->cstorereq(
1013 "open-ils.cstore.direct.asset.copy.retrieve", $id,
1015 flesh_fields => { acp => [ qw/ circ_lib location status stat_cat_entries / ] }
1018 $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND', id => $id) unless $copy;
1019 return ($copy, $evt);
1023 # returns the org that owns the callnumber that the copy
1025 sub fetch_copy_owner {
1026 my( $self, $copyid ) = @_;
1027 my( $copy, $cn, $evt );
1028 $logger->debug("Fetching copy owner $copyid");
1029 ($copy, $evt) = $self->fetch_copy($copyid);
1030 return (undef,$evt) if $evt;
1031 ($cn, $evt) = $self->fetch_callnumber($copy->call_number);
1032 return (undef,$evt) if $evt;
1033 return ($cn->owning_lib);
1036 sub fetch_copy_note {
1037 my( $self, $id ) = @_;
1039 $logger->debug("Fetching copy note $id");
1040 $note = $self->cstorereq(
1041 'open-ils.cstore.direct.asset.copy_note.retrieve', $id );
1042 $evt = OpenILS::Event->new('ASSET_COPY_NOTE_NOT_FOUND', id => $id ) unless $note;
1043 return ($note, $evt);
1046 sub fetch_call_numbers_by_title {
1047 my( $self, $titleid ) = @_;
1048 $logger->info("Fetching call numbers by title $titleid");
1049 return $self->cstorereq(
1050 'open-ils.cstore.direct.asset.call_number.search.atomic',
1051 { record => $titleid, deleted => 'f' });
1052 #'open-ils.storage.direct.asset.call_number.search.record.atomic', $titleid);
1055 sub fetch_copies_by_call_number {
1056 my( $self, $cnid ) = @_;
1057 $logger->info("Fetching copies by call number $cnid");
1058 return $self->cstorereq(
1059 'open-ils.cstore.direct.asset.copy.search.atomic', { call_number => $cnid, deleted => 'f' } );
1060 #'open-ils.storage.direct.asset.copy.search.call_number.atomic', $cnid );
1063 sub fetch_user_by_barcode {
1064 my( $self, $bc ) = @_;
1065 my $cardid = $self->cstorereq(
1066 'open-ils.cstore.direct.actor.card.id_list', { barcode => $bc } );
1067 return (undef, OpenILS::Event->new('ACTOR_CARD_NOT_FOUND', barcode => $bc)) unless $cardid;
1068 my $user = $self->cstorereq(
1069 'open-ils.cstore.direct.actor.user.search', { card => $cardid } );
1070 return (undef, OpenILS::Event->new('ACTOR_USER_NOT_FOUND', card => $cardid)) unless $user;
1076 my( $self, $billid ) = @_;
1077 $logger->debug("Fetching billing $billid");
1078 my $bill = $self->cstorereq(
1079 'open-ils.cstore.direct.money.billing.retrieve', $billid );
1080 my $evt = OpenILS::Event->new('MONEY_BILLING_NOT_FOUND') unless $bill;
1081 return($bill, $evt);
1085 sub fetch_org_tree {
1087 return $ORG_TREE if $ORG_TREE;
1088 return $ORG_TREE = OpenILS::Utils::CStoreEditor->new->search_actor_org_unit(
1090 {"parent_ou" => undef },
1093 flesh_fields => { aou => ['children'] },
1094 order_by => { aou => 'name'}
1101 my( $self, $node, $callback ) = @_;
1102 return unless $node;
1104 if( $node->children ) {
1105 $self->walk_org_tree($_, $callback) for @{$node->children};
1110 my( $self, $item ) = @_;
1111 return 1 if $item and $item !~ /^f$/i;
1116 # This logic now lives in storage
1117 sub __patron_money_owed {
1118 my( $self, $patronid ) = @_;
1119 my $ses = OpenSRF::AppSession->create('open-ils.storage');
1120 my $req = $ses->request(
1121 'open-ils.storage.money.billable_transaction.summary.search',
1122 { usr => $patronid, xact_finish => undef } );
1126 while( $data = $req->recv ) {
1127 $data = $data->content;
1128 $total += $data->balance_owed;
1133 sub patron_money_owed {
1134 my( $self, $userid ) = @_;
1135 my $ses = $self->start_db_session();
1136 my $val = $ses->request(
1137 'open-ils.storage.actor.user.total_owed', $userid)->gather(1);
1138 $self->rollback_db_session($ses);
1142 sub patron_total_items_out {
1143 my( $self, $userid ) = @_;
1144 my $ses = $self->start_db_session();
1145 my $val = $ses->request(
1146 'open-ils.storage.actor.user.total_out', $userid)->gather(1);
1147 $self->rollback_db_session($ses);
1154 #---------------------------------------------------------------------
1155 # Returns ($summary, $event)
1156 #---------------------------------------------------------------------
1160 my $e = shift || OpenILS::Utils::CStoreEditor->new;
1161 $id = $id->id if ref($id);
1163 my $xact = $e->retrieve_money_billable_transaction_summary($id)
1164 or return (undef, $e->event);
1170 #---------------------------------------------------------------------
1171 # Given a list of money.billable_transaction objects, this creates
1172 # transaction summary objects for each
1173 #--------------------------------------------------------------------
1178 return () if (!@xacts);
1179 return @{$e->search_money_billable_transaction_summary({id => [ map { $_->id } @xacts ]})};
1183 sub ou_ancestor_setting_value {
1184 my($self, $org_id, $name, $e) = @_;
1185 $e = $e || OpenILS::Utils::CStoreEditor->new;
1186 my $set = $self->ou_ancestor_setting($org_id, $name, $e);
1187 return $set->{value} if $set;
1191 sub ou_ancestor_setting {
1192 my( $self, $orgid, $name, $e ) = @_;
1193 $e = $e || OpenILS::Utils::CStoreEditor->new;
1194 my $query = {from => ['actor.org_unit_ancestor_setting', $name, $orgid]};
1195 my $setting = $e->json_query($query)->[0];
1196 return undef unless $setting;
1197 return {org => $setting->{org_unit}, value => OpenSRF::Utils::JSON->JSON2perl($setting->{value})};
1201 # returns the ISO8601 string representation of the requested epoch in GMT
1203 my( $self, $epoch ) = @_;
1204 my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($epoch);
1205 $year += 1900; $mon += 1;
1207 '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d-00',
1208 $year, $mon, $mday, $hour, $min, $sec);
1212 sub find_highest_perm_org {
1213 my ( $self, $perm, $userid, $start_org, $org_tree ) = @_;
1214 my $org = $self->find_org($org_tree, $start_org );
1218 last if ($self->check_perms( $userid, $org->id, $perm )); # perm failed
1220 $org = $self->find_org( $org_tree, $org->parent_ou() );
1227 # returns the org_unit ID's
1228 sub user_has_work_perm_at {
1229 my($self, $e, $perm, $options) = @_;
1232 my $func = 'permission.usr_has_perm_at';
1233 $func = $func.'_all' if $$options{descendants};
1235 my $orgs = $e->json_query({from => [$func, $e->requestor->id, $perm]});
1236 $orgs = [map { $_->{ (keys %$_)[0] } } @$orgs];
1238 return $orgs unless $$options{objects};
1240 return $e->search_actor_org_unit({id => $orgs});
1243 sub get_user_work_ou_ids {
1244 my($self, $e, $userid) = @_;
1245 my $work_orgs = $e->json_query({
1246 select => {puwoum => ['work_ou']},
1248 where => {usr => $e->requestor->id}});
1250 return [] unless @$work_orgs;
1252 push(@work_orgs, $_->{work_ou}) for @$work_orgs;
1260 my($self, $client) = @_;
1261 return $org_types if $org_types;
1262 return $org_types = OpenILS::Utils::CStoreEditor->new->retrieve_all_actor_org_unit_type();
1267 my $locale = shift || '';
1268 my $cache = OpenSRF::Utils::Cache->new("global", 0);
1269 my $tree = $cache->get_cache("orgtree.$locale");
1270 return $tree if $tree;
1272 my $ses = OpenILS::Utils::CStoreEditor->new;
1273 $ses->session->session_locale($locale);
1274 $tree = $ses->search_actor_org_unit(
1276 {"parent_ou" => undef },
1279 flesh_fields => { aou => ['children'] },
1280 order_by => { aou => 'name'}
1285 $cache->put_cache("orgtree.$locale", $tree);
1289 sub get_org_descendants {
1290 my($self, $org_id, $depth) = @_;
1293 transform => 'actor.org_unit_descendants',
1295 result_field => 'id',
1297 $select->{params} = [$depth] if defined $depth;
1299 my $org_list = OpenILS::Utils::CStoreEditor->new->json_query({
1300 select => {aou => [$select]},
1302 where => {id => $org_id}
1305 push(@orgs, $_->{id}) for @$org_list;
1309 sub get_org_ancestors {
1310 my($self, $org_id) = @_;
1312 my $org_list = OpenILS::Utils::CStoreEditor->new->json_query({
1315 transform => 'actor.org_unit_ancestors',
1317 result_field => 'id',
1322 where => {id => $org_id}
1326 push(@orgs, $_->{id}) for @$org_list;
1330 sub get_org_full_path {
1331 my($self, $org_id, $depth) = @_;
1334 my $org_list = OpenILS::Utils::CStoreEditor->new->json_query({
1337 transform => 'actor.org_unit_full_path',
1339 result_field => 'id',
1344 where => {id => $org_id}
1348 push(@orgs, $_->{id}) for @$org_list;
1352 # returns the user's configured locale as a string. Defaults to en-US if none is configured.
1353 sub get_user_locale {
1354 my($self, $user_id, $e) = @_;
1355 $e ||= OpenILS::Utils::CStoreEditor->new;
1357 # first, see if the user has an explicit locale set
1358 my $setting = $e->search_actor_user_setting(
1359 {usr => $user_id, name => 'global.locale'})->[0];
1360 return OpenSRF::Utils::JSON->JSON2perl($setting->value) if $setting;
1362 my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1363 return $self->get_org_locale($user->home_ou, $e);
1366 # returns org locale setting
1367 sub get_org_locale {
1368 my($self, $org_id, $e) = @_;
1369 $e ||= OpenILS::Utils::CStoreEditor->new;
1372 if(defined $org_id) {
1373 $locale = $self->ou_ancestor_setting_value($org_id, 'global.default_locale', $e);
1374 return $locale if $locale;
1377 # system-wide default
1378 my $sclient = OpenSRF::Utils::SettingsClient->new;
1379 $locale = $sclient->config_value('default_locale');
1380 return $locale if $locale;
1382 # if nothing else, fallback to locale=cowboy
1387 # xml-escape non-ascii characters
1389 my($self, $string, $form) = @_;
1393 $string = NFD($string);
1395 $string = NFC($string);
1398 # Convert raw ampersands to ampersand entities
1399 $string =~ s/&(?!\S+;)/&/gso;
1401 $string =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
1405 # x0000-x0008 isn't legal in XML documents
1406 # XXX Perhaps this should just go into our standard entityize method
1407 sub strip_ctrl_chars {
1408 my ($self, $string) = @_;
1410 $string =~ s/([\x{0000}-\x{0008}])//sgoe;
1414 sub get_copy_price {
1415 my($self, $e, $copy, $volume) = @_;
1417 $copy->price(0) if $copy->price and $copy->price < 0;
1419 return $copy->price if $copy->price and $copy->price > 0;
1424 if($volume->id == OILS_PRECAT_CALL_NUMBER) {
1425 $owner = $copy->circ_lib;
1427 $owner = $volume->owning_lib;
1430 if($copy->call_number == OILS_PRECAT_CALL_NUMBER) {
1431 $owner = $copy->circ_lib;
1433 $owner = $e->retrieve_asset_call_number($copy->call_number)->owning_lib;
1437 my $default_price = $self->ou_ancestor_setting_value(
1438 $owner, OILS_SETTING_DEF_ITEM_PRICE, $e) || 0;
1440 return $default_price unless defined $copy->price;
1442 # price is 0. Use the default?
1443 my $charge_on_0 = $self->ou_ancestor_setting_value(
1444 $owner, OILS_SETTING_CHARGE_LOST_ON_ZERO, $e) || 0;
1446 return $default_price if $charge_on_0;
1450 # given a transaction ID, this returns the context org_unit for the transaction
1452 my($self, $xact_id, $e) = @_;
1453 $e ||= OpenILS::Utils::CStoreEditor->new;
1455 my $loc = $e->json_query({
1456 "select" => {circ => ["circ_lib"]},
1458 "where" => {id => $xact_id},
1461 return $loc->[0]->{circ_lib} if @$loc;
1463 $loc = $e->json_query({
1464 "select" => {mg => ["billing_location"]},
1466 "where" => {id => $xact_id},
1469 return $loc->[0]->{billing_location};
1473 # If an event_def ID is not provided, use the hook and context org to find the
1474 # most appropriate event. create the event, fire it, then return the resulting
1475 # event with fleshed template_output and error_output
1476 sub fire_object_event {
1477 my($self, $event_def, $hook, $object, $context_org) = @_;
1479 my $e = OpenILS::Utils::CStoreEditor->new;
1483 $def = $e->retrieve_action_trigger_event_definition($event_def)
1484 or return $e->event;
1487 # find the most appropriate event def depending on context org
1488 my $orgs = $self->get_org_ancestors($context_org);
1489 $orgs = $e->search_actor_org_unit(
1490 [{id => $orgs}, {flesh => 1, flesh_fields => {aou => ['ou_type']}}]);
1491 $orgs = [ sort { $a->ou_type->depth cmp $b->ou_type->depth } @$orgs ];
1493 for my $org (reverse @$orgs) {
1494 $def = $e->search_action_trigger_event_definition(
1495 {hook => $hook, owner => $org->id}
1500 return $e->event unless $def;
1503 my $event_id = $self->simplereq(
1505 'open-ils.trigger.event.autocreate.by_definition',
1506 $def->id, $object, $context_org);
1508 my $fire = 'open-ils.trigger.event.fire';
1510 if($def->group_field) {
1511 $fire =~ s/event/event_group/o;
1512 $event_id = [$event_id];
1515 my $resp = $self->simplereq('open-ils.trigger', $fire, $event_id);
1516 return 0 unless $resp and ($resp->{event} or $resp->{events});
1517 my $evt = $resp->{event} ? $resp->{event} : $resp->{events}->[0];
1519 return 0 unless $evt;
1521 return $e->retrieve_action_trigger_event([
1523 {flesh => 1, flesh_fields => {atev => ['template_output', 'error_output']}}