]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/AppUtils.pm
subclassing OpenSRF::Application so we have a place to put global OpenILS methods
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / AppUtils.pm
1 package OpenILS::Application::AppUtils;
2 # vim:noet:ts=4
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);
10 use OpenILS::Event;
11 use Data::Dumper;
12 use OpenILS::Utils::CStoreEditor;
13 use OpenILS::Const qw/:const/;
14
15 # ---------------------------------------------------------------------------
16 # Pile of utilty methods used accross applications.
17 # ---------------------------------------------------------------------------
18 my $cache_client = "OpenSRF::Utils::Cache";
19
20
21 # ---------------------------------------------------------------------------
22 # on sucess, returns the created session, on failure throws ERROR exception
23 # ---------------------------------------------------------------------------
24 sub start_db_session {
25
26         my $self = shift;
27         my $session = OpenSRF::AppSession->connect( "open-ils.storage" );
28         my $trans_req = $session->request( "open-ils.storage.transaction.begin" );
29
30         my $trans_resp = $trans_req->recv();
31         if(ref($trans_resp) and UNIVERSAL::isa($trans_resp,"Error")) { throw $trans_resp; }
32         if( ! $trans_resp->content() ) {
33                 throw OpenSRF::ERROR 
34                         ("Unable to Begin Transaction with database" );
35         }
36         $trans_req->finish();
37
38         $logger->debug("Setting global storage session to ".
39                 "session: " . $session->session_id . " : " . $session->app );
40
41         return $session;
42 }
43
44
45 # returns undef if user has all of the perms provided
46 # returns the first failed perm on failure
47 sub check_user_perms {
48         my($self, $user_id, $org_id, @perm_types ) = @_;
49         $logger->debug("Checking perms with user : $user_id , org: $org_id, @perm_types");
50         for my $type (@perm_types) {
51                 return $type unless ($self->storagereq(
52                         "open-ils.storage.permission.user_has_perm", 
53                         $user_id, $type, $org_id ));
54         }
55         return undef;
56 }
57
58 # checks the list of user perms.  The first one that fails returns a new
59 sub check_perms {
60         my( $self, $user_id, $org_id, @perm_types ) = @_;
61         my $t = $self->check_user_perms( $user_id, $org_id, @perm_types );
62         return OpenILS::Event->new('PERM_FAILURE', ilsperm => $t, ilspermloc => $org_id ) if $t;
63         return undef;
64 }
65
66
67
68 # ---------------------------------------------------------------------------
69 # commits and destroys the session
70 # ---------------------------------------------------------------------------
71 sub commit_db_session {
72         my( $self, $session ) = @_;
73
74         my $req = $session->request( "open-ils.storage.transaction.commit" );
75         my $resp = $req->recv();
76
77         if(!$resp) {
78                 throw OpenSRF::EX::ERROR ("Unable to commit db session");
79         }
80
81         if(UNIVERSAL::isa($resp,"Error")) { 
82                 throw $resp ($resp->stringify); 
83         }
84
85         if(!$resp->content) {
86                 throw OpenSRF::EX::ERROR ("Unable to commit db session");
87         }
88
89         $session->finish();
90         $session->disconnect();
91         $session->kill_me();
92 }
93
94 sub rollback_db_session {
95         my( $self, $session ) = @_;
96
97         my $req = $session->request("open-ils.storage.transaction.rollback");
98         my $resp = $req->recv();
99         if(UNIVERSAL::isa($resp,"Error")) { throw $resp;  }
100
101         $session->finish();
102         $session->disconnect();
103         $session->kill_me();
104 }
105
106
107 # returns undef it the event is not an ILS event
108 # returns the event code otherwise
109 sub event_code {
110         my( $self, $evt ) = @_;
111         return $evt->{ilsevent} if( ref($evt) eq 'HASH' and defined($evt->{ilsevent})) ;
112         return undef;
113 }
114
115 # ---------------------------------------------------------------------------
116 # Checks to see if a user is logged in.  Returns the user record on success,
117 # throws an exception on error.
118 # ---------------------------------------------------------------------------
119 sub check_user_session {
120
121         my( $self, $user_session ) = @_;
122
123         my $content = $self->simplereq( 
124                 'open-ils.auth', 
125                 'open-ils.auth.session.retrieve', $user_session );
126
127         if(! $content or $self->event_code($content)) {
128                 throw OpenSRF::EX::ERROR 
129                         ("Session [$user_session] cannot be authenticated" );
130         }
131
132         $logger->debug("Fetch user session $user_session found user " . $content->id );
133
134         return $content;
135 }
136
137 # generic simple request returning a scalar value
138 sub simplereq {
139         my($self, $service, $method, @params) = @_;
140         return $self->simple_scalar_request($service, $method, @params);
141 }
142
143
144 sub simple_scalar_request {
145         my($self, $service, $method, @params) = @_;
146
147         my $session = OpenSRF::AppSession->create( $service );
148
149         my $request = $session->request( $method, @params );
150
151         my $val;
152         my $err;
153         try  {
154
155                 $val = $request->gather(1);     
156
157         } catch Error with {
158                 $err = shift;
159         };
160
161         if( $err ) {
162                 warn "received error : service=$service : method=$method : params=".Dumper(\@params) . "\n $err";
163                 throw $err ("Call to $service for method $method \n failed with exception: $err : " );
164         }
165
166         return $val;
167 }
168
169
170
171
172
173 my $tree                                                = undef;
174 my $orglist                                     = undef;
175 my $org_typelist                        = undef;
176 my $org_typelist_hash   = {};
177
178 sub get_org_tree {
179
180         my $self = shift;
181         if($tree) { return $tree; }
182
183         # see if it's in the cache
184         $tree = $cache_client->new()->get_cache('_orgtree');
185         if($tree) { return $tree; }
186
187         if(!$orglist) {
188                 warn "Retrieving Org Tree\n";
189                 $orglist = $self->simple_scalar_request( 
190                         "open-ils.cstore", 
191                         "open-ils.cstore.direct.actor.org_unit.search.atomic",
192                         { id => { '!=' => undef } }
193                 );
194         }
195
196         if( ! $org_typelist ) {
197                 warn "Retrieving org types\n";
198                 $org_typelist = $self->simple_scalar_request( 
199                         "open-ils.cstore", 
200                         "open-ils.cstore.direct.actor.org_unit_type.search.atomic",
201                         { id => { '!=' => undef } }
202                 );
203                 $self->build_org_type($org_typelist);
204         }
205
206         $tree = $self->build_org_tree($orglist,1);
207         $cache_client->new()->put_cache('_orgtree', $tree);
208         return $tree;
209
210 }
211
212 my $slimtree = undef;
213 sub get_slim_org_tree {
214
215         my $self = shift;
216         if($slimtree) { return $slimtree; }
217
218         # see if it's in the cache
219         $slimtree = $cache_client->new()->get_cache('slimorgtree');
220         if($slimtree) { return $slimtree; }
221
222         if(!$orglist) {
223                 warn "Retrieving Org Tree\n";
224                 $orglist = $self->simple_scalar_request( 
225                         "open-ils.cstore", 
226                         "open-ils.cstore.direct.actor.org_unit.search.atomic",
227                         { id => { '!=' => undef } }
228                 );
229         }
230
231         $slimtree = $self->build_org_tree($orglist);
232         $cache_client->new->put_cache('slimorgtree', $slimtree);
233         return $slimtree;
234
235 }
236
237
238 sub build_org_type { 
239         my($self, $org_typelist)  = @_;
240         for my $type (@$org_typelist) {
241                 $org_typelist_hash->{$type->id()} = $type;
242         }
243 }
244
245
246
247 sub build_org_tree {
248
249         my( $self, $orglist, $add_types ) = @_;
250
251         return $orglist unless ref $orglist; 
252     return $$orglist[0] if @$orglist == 1;
253
254         my @list = sort { 
255                 $a->ou_type <=> $b->ou_type ||
256                 $a->name cmp $b->name } @$orglist;
257
258         for my $org (@list) {
259
260                 next unless ($org);
261
262                 if(!ref($org->ou_type()) and $add_types) {
263                         $org->ou_type( $org_typelist_hash->{$org->ou_type()});
264                 }
265
266                 next unless (defined($org->parent_ou));
267
268                 my ($parent) = grep { $_->id == $org->parent_ou } @list;
269                 next unless $parent;
270                 $parent->children([]) unless defined($parent->children); 
271                 push( @{$parent->children}, $org );
272         }
273
274         return $list[0];
275 }
276
277 sub fetch_closed_date {
278         my( $self, $cd ) = @_;
279         my $evt;
280         
281         $logger->debug("Fetching closed_date $cd from cstore");
282
283         my $cd_obj = $self->simplereq(
284                 'open-ils.cstore',
285                 'open-ils.cstore.direct.actor.org_unit.closed_date.retrieve', $cd );
286
287         if(!$cd_obj) {
288                 $logger->info("closed_date $cd not found in the db");
289                 $evt = OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
290         }
291
292         return ($cd_obj, $evt);
293 }
294
295 sub fetch_user {
296         my( $self, $userid ) = @_;
297         my( $user, $evt );
298         
299         $logger->debug("Fetching user $userid from cstore");
300
301         $user = $self->simplereq(
302                 'open-ils.cstore',
303                 'open-ils.cstore.direct.actor.user.retrieve', $userid );
304
305         if(!$user) {
306                 $logger->info("User $userid not found in the db");
307                 $evt = OpenILS::Event->new('ACTOR_USER_NOT_FOUND');
308         }
309
310         return ($user, $evt);
311 }
312
313 sub checkses {
314         my( $self, $session ) = @_;
315         my $user; my $evt; my $e; 
316
317         $logger->debug("Checking user session $session");
318
319         try {
320                 $user = $self->check_user_session($session);
321         } catch Error with { $e = 1; };
322
323         $logger->debug("Done checking user session $session " . (($e) ? "error = $e" : "") );
324
325         if( $e or !$user ) { $evt = OpenILS::Event->new('NO_SESSION'); }
326         return ( $user, $evt );
327 }
328
329
330 # verifiese the session and checks the permissions agains the
331 # session user and the user's home_ou as the org id
332 sub checksesperm {
333         my( $self, $session, @perms ) = @_;
334         my $user; my $evt; my $e; 
335         $logger->debug("Checking user session $session and perms @perms");
336         ($user, $evt) = $self->checkses($session);
337         return (undef, $evt) if $evt;
338         $evt = $self->check_perms($user->id, $user->home_ou, @perms);
339         return ($user, $evt);
340 }
341
342
343 sub checkrequestor {
344         my( $self, $staffobj, $userid, @perms ) = @_;
345         my $user; my $evt;
346         $userid = $staffobj->id unless defined $userid;
347
348         $logger->debug("checkrequestor(): requestor => " . $staffobj->id . ", target => $userid");
349
350         if( $userid ne $staffobj->id ) {
351                 ($user, $evt) = $self->fetch_user($userid);
352                 return (undef, $evt) if $evt;
353                 $evt = $self->check_perms( $staffobj->id, $user->home_ou, @perms );
354
355         } else {
356                 $user = $staffobj;
357         }
358
359         return ($user, $evt);
360 }
361
362 sub checkses_requestor {
363         my( $self, $authtoken, $targetid, @perms ) = @_;
364         my( $requestor, $target, $evt );
365
366         ($requestor, $evt) = $self->checkses($authtoken);
367         return (undef, undef, $evt) if $evt;
368
369         ($target, $evt) = $self->checkrequestor( $requestor, $targetid, @perms );
370         return( $requestor, $target, $evt);
371 }
372
373 sub fetch_copy {
374         my( $self, $copyid ) = @_;
375         my( $copy, $evt );
376
377         $logger->debug("Fetching copy $copyid from cstore");
378
379         $copy = $self->simplereq(
380                 'open-ils.cstore',
381                 'open-ils.cstore.direct.asset.copy.retrieve', $copyid );
382
383         if(!$copy) { $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND'); }
384
385         return( $copy, $evt );
386 }
387
388
389 # retrieves a circ object by id
390 sub fetch_circulation {
391         my( $self, $circid ) = @_;
392         my $circ; my $evt;
393         
394         $logger->debug("Fetching circ $circid from cstore");
395
396         $circ = $self->simplereq(
397                 'open-ils.cstore',
398                 "open-ils.cstore.direct.action.circulation.retrieve", $circid );
399
400         if(!$circ) {
401                 $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND', circid => $circid );
402         }
403
404         return ( $circ, $evt );
405 }
406
407 sub fetch_record_by_copy {
408         my( $self, $copyid ) = @_;
409         my( $record, $evt );
410
411         $logger->debug("Fetching record by copy $copyid from cstore");
412
413         $record = $self->simplereq(
414                 'open-ils.cstore',
415                 'open-ils.cstore.direct.asset.copy.retrieve', $copyid,
416                 { flesh => 3,
417                   flesh_fields => {     bre => [ 'fixed_fields' ],
418                                         acn => [ 'record' ],
419                                         acp => [ 'call_number' ],
420                                   }
421                 }
422         );
423
424         if(!$record) {
425                 $evt = OpenILS::Event->new('BIBLIO_RECORD_ENTRY_NOT_FOUND');
426         } else {
427                 $record = $record->call_number->record;
428         }
429
430         return ($record, $evt);
431 }
432
433 # turns a record object into an mvr (mods) object
434 sub record_to_mvr {
435         my( $self, $record ) = @_;
436         return undef unless $record and $record->marc;
437         my $u = OpenILS::Utils::ModsParser->new();
438         $u->start_mods_batch( $record->marc );
439         my $mods = $u->finish_mods_batch();
440         $mods->doc_id($record->id);
441    $mods->tcn($record->tcn_value);
442         return $mods;
443 }
444
445 sub fetch_hold {
446         my( $self, $holdid ) = @_;
447         my( $hold, $evt );
448
449         $logger->debug("Fetching hold $holdid from cstore");
450
451         $hold = $self->simplereq(
452                 'open-ils.cstore',
453                 'open-ils.cstore.direct.action.hold_request.retrieve', $holdid);
454
455         $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND', holdid => $holdid) unless $hold;
456
457         return ($hold, $evt);
458 }
459
460
461 sub fetch_hold_transit_by_hold {
462         my( $self, $holdid ) = @_;
463         my( $transit, $evt );
464
465         $logger->debug("Fetching transit by hold $holdid from cstore");
466
467         $transit = $self->simplereq(
468                 'open-ils.cstore',
469                 'open-ils.cstore.direct.action.hold_transit_copy.search', { hold => $holdid } );
470
471         $evt = OpenILS::Event->new('ACTION_HOLD_TRANSIT_COPY_NOT_FOUND', holdid => $holdid) unless $transit;
472
473         return ($transit, $evt );
474 }
475
476 # fetches the captured, but not fulfilled hold attached to a given copy
477 sub fetch_open_hold_by_copy {
478         my( $self, $copyid ) = @_;
479         $logger->debug("Searching for active hold for copy $copyid");
480         my( $hold, $evt );
481
482         $hold = $self->cstorereq(
483                 'open-ils.cstore.direct.action.hold_request.search',
484                 { 
485                         current_copy            => $copyid , 
486                         capture_time            => { "!=" => undef }, 
487                         fulfillment_time        => undef,
488                         cancel_time                     => undef,
489                 } );
490
491         $evt = OpenILS::Event->new('ACTION_HOLD_REQUEST_NOT_FOUND', copyid => $copyid) unless $hold;
492         return ($hold, $evt);
493 }
494
495 sub fetch_hold_transit {
496         my( $self, $transid ) = @_;
497         my( $htransit, $evt );
498         $logger->debug("Fetching hold transit with hold id $transid");
499         $htransit = $self->cstorereq(
500                 'open-ils.cstore.direct.action.hold_transit_copy.retrieve', $transid );
501         $evt = OpenILS::Event->new('ACTION_HOLD_TRANSIT_COPY_NOT_FOUND', id => $transid) unless $htransit;
502         return ($htransit, $evt);
503 }
504
505 sub fetch_copy_by_barcode {
506         my( $self, $barcode ) = @_;
507         my( $copy, $evt );
508
509         $logger->debug("Fetching copy by barcode $barcode from cstore");
510
511         $copy = $self->simplereq( 'open-ils.cstore',
512                 'open-ils.cstore.direct.asset.copy.search', { barcode => $barcode, deleted => 'f'} );
513                 #'open-ils.storage.direct.asset.copy.search.barcode', $barcode );
514
515         $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND', barcode => $barcode) unless $copy;
516
517         return ($copy, $evt);
518 }
519
520 sub fetch_open_billable_transaction {
521         my( $self, $transid ) = @_;
522         my( $transaction, $evt );
523
524         $logger->debug("Fetching open billable transaction $transid from cstore");
525
526         $transaction = $self->simplereq(
527                 'open-ils.cstore',
528                 'open-ils.cstore.direct.money.open_billable_transaction_summary.retrieve',  $transid);
529
530         $evt = OpenILS::Event->new(
531                 'MONEY_OPEN_BILLABLE_TRANSACTION_SUMMARY_NOT_FOUND', transid => $transid ) unless $transaction;
532
533         return ($transaction, $evt);
534 }
535
536
537
538 my %buckets;
539 $buckets{'biblio'} = 'biblio_record_entry_bucket';
540 $buckets{'callnumber'} = 'call_number_bucket';
541 $buckets{'copy'} = 'copy_bucket';
542 $buckets{'user'} = 'user_bucket';
543
544 sub fetch_container {
545         my( $self, $id, $type ) = @_;
546         my( $bucket, $evt );
547
548         $logger->debug("Fetching container $id with type $type");
549
550         my $e = 'CONTAINER_CALL_NUMBER_BUCKET_NOT_FOUND';
551         $e = 'CONTAINER_BIBLIO_RECORD_ENTRY_BUCKET_NOT_FOUND' if $type eq 'biblio';
552         $e = 'CONTAINER_USER_BUCKET_NOT_FOUND' if $type eq 'user';
553         $e = 'CONTAINER_COPY_BUCKET_NOT_FOUND' if $type eq 'copy';
554
555         my $meth = $buckets{$type};
556         $bucket = $self->simplereq(
557                 'open-ils.cstore',
558                 "open-ils.cstore.direct.container.$meth.retrieve", $id );
559
560         $evt = OpenILS::Event->new(
561                 $e, container => $id, container_type => $type ) unless $bucket;
562
563         return ($bucket, $evt);
564 }
565
566
567 sub fetch_container_e {
568         my( $self, $editor, $id, $type ) = @_;
569
570         my( $bucket, $evt );
571         $bucket = $editor->retrieve_container_copy_bucket($id) if $type eq 'copy';
572         $bucket = $editor->retrieve_container_call_number_bucket($id) if $type eq 'callnumber';
573         $bucket = $editor->retrieve_container_biblio_record_entry_bucket($id) if $type eq 'biblio';
574         $bucket = $editor->retrieve_container_user_bucket($id) if $type eq 'user';
575
576         $evt = $editor->event unless $bucket;
577         return ($bucket, $evt);
578 }
579
580 sub fetch_container_item_e {
581         my( $self, $editor, $id, $type ) = @_;
582
583         my( $bucket, $evt );
584         $bucket = $editor->retrieve_container_copy_bucket_item($id) if $type eq 'copy';
585         $bucket = $editor->retrieve_container_call_number_bucket_item($id) if $type eq 'callnumber';
586         $bucket = $editor->retrieve_container_biblio_record_entry_bucket_item($id) if $type eq 'biblio';
587         $bucket = $editor->retrieve_container_user_bucket_item($id) if $type eq 'user';
588
589         $evt = $editor->event unless $bucket;
590         return ($bucket, $evt);
591 }
592
593
594
595
596
597 sub fetch_container_item {
598         my( $self, $id, $type ) = @_;
599         my( $bucket, $evt );
600
601         $logger->debug("Fetching container item $id with type $type");
602
603         my $meth = $buckets{$type} . "_item";
604
605         $bucket = $self->simplereq(
606                 'open-ils.cstore',
607                 "open-ils.cstore.direct.container.$meth.retrieve", $id );
608
609
610         my $e = 'CONTAINER_CALL_NUMBER_BUCKET_ITEM_NOT_FOUND';
611         $e = 'CONTAINER_BIBLIO_RECORD_ENTRY_BUCKET_ITEM_NOT_FOUND' if $type eq 'biblio';
612         $e = 'CONTAINER_USER_BUCKET_ITEM_NOT_FOUND' if $type eq 'user';
613         $e = 'CONTAINER_COPY_BUCKET_ITEM_NOT_FOUND' if $type eq 'copy';
614
615         $evt = OpenILS::Event->new(
616                 $e, itemid => $id, container_type => $type ) unless $bucket;
617
618         return ($bucket, $evt);
619 }
620
621
622 sub fetch_patron_standings {
623         my $self = shift;
624         $logger->debug("Fetching patron standings");    
625         return $self->simplereq(
626                 'open-ils.cstore', 
627                 'open-ils.cstore.direct.config.standing.search.atomic', { id => { '!=' => undef } });
628 }
629
630
631 sub fetch_permission_group_tree {
632         my $self = shift;
633         $logger->debug("Fetching patron profiles");     
634         return $self->simplereq(
635                 'open-ils.actor', 
636                 'open-ils.actor.groups.tree.retrieve' );
637 }
638
639
640 sub fetch_patron_circ_summary {
641         my( $self, $userid ) = @_;
642         $logger->debug("Fetching patron summary for $userid");
643         my $summary = $self->simplereq(
644                 'open-ils.storage', 
645                 "open-ils.storage.action.circulation.patron_summary", $userid );
646
647         if( $summary ) {
648                 $summary->[0] ||= 0;
649                 $summary->[1] ||= 0.0;
650                 return $summary;
651         }
652         return undef;
653 }
654
655
656 sub fetch_copy_statuses {
657         my( $self ) = @_;
658         $logger->debug("Fetching copy statuses");
659         return $self->simplereq(
660                 'open-ils.cstore', 
661                 'open-ils.cstore.direct.config.copy_status.search.atomic', { id => { '!=' => undef } });
662 }
663
664 sub fetch_copy_location {
665         my( $self, $id ) = @_;
666         my $evt;
667         my $cl = $self->cstorereq(
668                 'open-ils.cstore.direct.asset.copy_location.retrieve', $id );
669         $evt = OpenILS::Event->new('ASSET_COPY_LOCATION_NOT_FOUND') unless $cl;
670         return ($cl, $evt);
671 }
672
673 sub fetch_copy_locations {
674         my $self = shift; 
675         return $self->simplereq(
676                 'open-ils.cstore', 
677                 'open-ils.cstore.direct.asset.copy_location.search.atomic', { id => { '!=' => undef } });
678 }
679
680 sub fetch_copy_location_by_name {
681         my( $self, $name, $org ) = @_;
682         my $evt;
683         my $cl = $self->cstorereq(
684                 'open-ils.cstore.direct.asset.copy_location.search',
685                         { name => $name, owning_lib => $org } );
686         $evt = OpenILS::Event->new('ASSET_COPY_LOCATION_NOT_FOUND') unless $cl;
687         return ($cl, $evt);
688 }
689
690 sub fetch_callnumber {
691         my( $self, $id ) = @_;
692         my $evt = undef;
693
694         my $e = OpenILS::Event->new( 'ASSET_CALL_NUMBER_NOT_FOUND', id => $id );
695         return( undef, $e ) unless $id;
696
697         $logger->debug("Fetching callnumber $id");
698
699         my $cn = $self->simplereq(
700                 'open-ils.cstore',
701                 'open-ils.cstore.direct.asset.call_number.retrieve', $id );
702         $evt = $e  unless $cn;
703
704         return ( $cn, $evt );
705 }
706
707 my %ORG_CACHE; # - these rarely change, so cache them..
708 sub fetch_org_unit {
709         my( $self, $id ) = @_;
710         return undef unless $id;
711         return $id if( ref($id) eq 'Fieldmapper::actor::org_unit' );
712         return $ORG_CACHE{$id} if $ORG_CACHE{$id};
713         $logger->debug("Fetching org unit $id");
714         my $evt = undef;
715
716         my $org = $self->simplereq(
717                 'open-ils.cstore', 
718                 'open-ils.cstore.direct.actor.org_unit.retrieve', $id );
719         $evt = OpenILS::Event->new( 'ACTOR_ORG_UNIT_NOT_FOUND', id => $id ) unless $org;
720         $ORG_CACHE{$id}  = $org;
721
722         return ($org, $evt);
723 }
724
725 sub fetch_stat_cat {
726         my( $self, $type, $id ) = @_;
727         my( $cat, $evt );
728         $logger->debug("Fetching $type stat cat: $id");
729         $cat = $self->simplereq(
730                 'open-ils.cstore', 
731                 "open-ils.cstore.direct.$type.stat_cat.retrieve", $id );
732
733         my $e = 'ASSET_STAT_CAT_NOT_FOUND';
734         $e = 'ACTOR_STAT_CAT_NOT_FOUND' if $type eq 'actor';
735
736         $evt = OpenILS::Event->new( $e, id => $id ) unless $cat;
737         return ( $cat, $evt );
738 }
739
740 sub fetch_stat_cat_entry {
741         my( $self, $type, $id ) = @_;
742         my( $entry, $evt );
743         $logger->debug("Fetching $type stat cat entry: $id");
744         $entry = $self->simplereq(
745                 'open-ils.cstore', 
746                 "open-ils.cstore.direct.$type.stat_cat_entry.retrieve", $id );
747
748         my $e = 'ASSET_STAT_CAT_ENTRY_NOT_FOUND';
749         $e = 'ACTOR_STAT_CAT_ENTRY_NOT_FOUND' if $type eq 'actor';
750
751         $evt = OpenILS::Event->new( $e, id => $id ) unless $entry;
752         return ( $entry, $evt );
753 }
754
755
756 sub find_org {
757         my( $self, $org_tree, $orgid )  = @_;
758         if (!$org_tree) {
759                 $logger->warn("find_org() did not receive a value for \$org_tree");
760                 return undef;
761         } elsif (!$orgid) {
762                 $logger->warn("find_org() did not receive a value for \$orgid");
763                 return undef;
764     }
765         return $org_tree if ( $org_tree->id eq $orgid );
766         return undef unless ref($org_tree->children);
767         for my $c (@{$org_tree->children}) {
768                 my $o = $self->find_org($c, $orgid);
769                 return $o if $o;
770         }
771         return undef;
772 }
773
774 sub fetch_non_cat_type_by_name_and_org {
775         my( $self, $name, $orgId ) = @_;
776         $logger->debug("Fetching non cat type $name at org $orgId");
777         my $types = $self->simplereq(
778                 'open-ils.cstore',
779                 'open-ils.cstore.direct.config.non_cataloged_type.search.atomic',
780                 { name => $name, owning_lib => $orgId } );
781         return ($types->[0], undef) if($types and @$types);
782         return (undef, OpenILS::Event->new('CONFIG_NON_CATALOGED_TYPE_NOT_FOUND') );
783 }
784
785 sub fetch_non_cat_type {
786         my( $self, $id ) = @_;
787         $logger->debug("Fetching non cat type $id");
788         my( $type, $evt );
789         $type = $self->simplereq(
790                 'open-ils.cstore', 
791                 'open-ils.cstore.direct.config.non_cataloged_type.retrieve', $id );
792         $evt = OpenILS::Event->new('CONFIG_NON_CATALOGED_TYPE_NOT_FOUND') unless $type;
793         return ($type, $evt);
794 }
795
796 sub DB_UPDATE_FAILED { 
797         my( $self, $payload ) = @_;
798         return OpenILS::Event->new('DATABASE_UPDATE_FAILED', 
799                 payload => ($payload) ? $payload : undef ); 
800 }
801
802 sub fetch_circ_duration_by_name {
803         my( $self, $name ) = @_;
804         my( $dur, $evt );
805         $dur = $self->simplereq(
806                 'open-ils.cstore', 
807                 'open-ils.cstore.direct.config.rules.circ_duration.search.atomic', { name => $name } );
808         $dur = $dur->[0];
809         $evt = OpenILS::Event->new('CONFIG_RULES_CIRC_DURATION_NOT_FOUND') unless $dur;
810         return ($dur, $evt);
811 }
812
813 sub fetch_recurring_fine_by_name {
814         my( $self, $name ) = @_;
815         my( $obj, $evt );
816         $obj = $self->simplereq(
817                 'open-ils.cstore', 
818                 'open-ils.cstore.direct.config.rules.recuring_fine.search.atomic', { name => $name } );
819         $obj = $obj->[0];
820         $evt = OpenILS::Event->new('CONFIG_RULES_RECURING_FINE_NOT_FOUND') unless $obj;
821         return ($obj, $evt);
822 }
823
824 sub fetch_max_fine_by_name {
825         my( $self, $name ) = @_;
826         my( $obj, $evt );
827         $obj = $self->simplereq(
828                 'open-ils.cstore', 
829                 'open-ils.cstore.direct.config.rules.max_fine.search.atomic', { name => $name } );
830         $obj = $obj->[0];
831         $evt = OpenILS::Event->new('CONFIG_RULES_MAX_FINE_NOT_FOUND') unless $obj;
832         return ($obj, $evt);
833 }
834
835 sub storagereq {
836         my( $self, $method, @params ) = @_;
837         return $self->simplereq(
838                 'open-ils.storage', $method, @params );
839 }
840
841 sub cstorereq {
842         my( $self, $method, @params ) = @_;
843         return $self->simplereq(
844                 'open-ils.cstore', $method, @params );
845 }
846
847 sub event_equals {
848         my( $self, $e, $name ) =  @_;
849         if( $e and ref($e) eq 'HASH' and 
850                 defined($e->{textcode}) and $e->{textcode} eq $name ) {
851                 return 1 ;
852         }
853         return 0;
854 }
855
856 sub logmark {
857         my( undef, $f, $l ) = caller(0);
858         my( undef, undef, undef, $s ) = caller(1);
859         $s =~ s/.*:://g;
860         $f =~ s/.*\///g;
861         $logger->debug("LOGMARK: $f:$l:$s");
862 }
863
864 # takes a copy id 
865 sub fetch_open_circulation {
866         my( $self, $cid ) = @_;
867         my $evt;
868         $self->logmark;
869         my $circ = $self->cstorereq(
870                 'open-ils.cstore.direct.action.open_circulation.search',
871                 { target_copy => $cid, stop_fines_time => undef } );
872         $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND') unless $circ;        
873         return ($circ, $evt);
874 }
875
876 sub fetch_all_open_circulation {
877         my( $self, $cid ) = @_;
878         my $evt;
879         $self->logmark;
880         my $circ = $self->cstorereq(
881                 'open-ils.cstore.direct.action.open_circulation.search',
882                 { target_copy => $cid, xact_finish => undef } );
883         $evt = OpenILS::Event->new('ACTION_CIRCULATION_NOT_FOUND') unless $circ;        
884         return ($circ, $evt);
885 }
886
887 my $copy_statuses;
888 sub copy_status_from_name {
889         my( $self, $name ) = @_;
890         $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
891         for my $status (@$copy_statuses) { 
892                 return $status if( $status->name =~ /$name/i );
893         }
894         return undef;
895 }
896
897 sub copy_status_to_name {
898         my( $self, $sid ) = @_;
899         $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
900         for my $status (@$copy_statuses) { 
901                 return $status->name if( $status->id == $sid );
902         }
903         return undef;
904 }
905
906
907 sub copy_status {
908         my( $self, $arg ) = @_;
909         return $arg if ref $arg;
910         $copy_statuses = $self->fetch_copy_statuses unless $copy_statuses;
911         my ($stat) = grep { $_->id == $arg } @$copy_statuses;
912         return $stat;
913 }
914
915 sub fetch_open_transit_by_copy {
916         my( $self, $copyid ) = @_;
917         my($transit, $evt);
918         $transit = $self->cstorereq(
919                 'open-ils.cstore.direct.action.transit_copy.search',
920                 { target_copy => $copyid, dest_recv_time => undef });
921         $evt = OpenILS::Event->new('ACTION_TRANSIT_COPY_NOT_FOUND') unless $transit;
922         return ($transit, $evt);
923 }
924
925 sub unflesh_copy {
926         my( $self, $copy ) = @_;
927         return undef unless $copy;
928         $copy->status( $copy->status->id ) if ref($copy->status);
929         $copy->location( $copy->location->id ) if ref($copy->location);
930         $copy->circ_lib( $copy->circ_lib->id ) if ref($copy->circ_lib);
931         return $copy;
932 }
933
934 # un-fleshes a copy and updates it in the DB
935 # returns a DB_UPDATE_FAILED event on error
936 # returns undef on success
937 sub update_copy {
938         my( $self, %params ) = @_;
939
940         my $copy                = $params{copy} || die "update_copy(): copy required";
941         my $editor      = $params{editor} || die "update_copy(): copy editor required";
942         my $session = $params{session};
943
944         $logger->debug("Updating copy in the database: " . $copy->id);
945
946         $self->unflesh_copy($copy);
947         $copy->editor( $editor );
948         $copy->edit_date( 'now' );
949
950         my $s;
951         my $meth = 'open-ils.storage.direct.asset.copy.update';
952
953         $s = $session->request( $meth, $copy )->gather(1) if $session;
954         $s = $self->storagereq( $meth, $copy ) unless $session;
955
956         $logger->debug("Update of copy ".$copy->id." returned: $s");
957
958         return $self->DB_UPDATE_FAILED($copy) unless $s;
959         return undef;
960 }
961
962 sub fetch_billable_xact {
963         my( $self, $id ) = @_;
964         my($xact, $evt);
965         $logger->debug("Fetching billable transaction %id");
966         $xact = $self->cstorereq(
967                 'open-ils.cstore.direct.money.billable_transaction.retrieve', $id );
968         $evt = OpenILS::Event->new('MONEY_BILLABLE_TRANSACTION_NOT_FOUND') unless $xact;
969         return ($xact, $evt);
970 }
971
972 sub fetch_billable_xact_summary {
973         my( $self, $id ) = @_;
974         my($xact, $evt);
975         $logger->debug("Fetching billable transaction summary %id");
976         $xact = $self->cstorereq(
977                 'open-ils.cstore.direct.money.billable_transaction_summary.retrieve', $id );
978         $evt = OpenILS::Event->new('MONEY_BILLABLE_TRANSACTION_NOT_FOUND') unless $xact;
979         return ($xact, $evt);
980 }
981
982 sub fetch_fleshed_copy {
983         my( $self, $id ) = @_;
984         my( $copy, $evt );
985         $logger->info("Fetching fleshed copy $id");
986         $copy = $self->cstorereq(
987                 "open-ils.cstore.direct.asset.copy.retrieve", $id,
988                 { flesh => 1,
989                   flesh_fields => { acp => [ qw/ circ_lib location status stat_cat_entries / ] }
990                 }
991         );
992         $evt = OpenILS::Event->new('ASSET_COPY_NOT_FOUND', id => $id) unless $copy;
993         return ($copy, $evt);
994 }
995
996
997 # returns the org that owns the callnumber that the copy
998 # is attached to
999 sub fetch_copy_owner {
1000         my( $self, $copyid ) = @_;
1001         my( $copy, $cn, $evt );
1002         $logger->debug("Fetching copy owner $copyid");
1003         ($copy, $evt) = $self->fetch_copy($copyid);
1004         return (undef,$evt) if $evt;
1005         ($cn, $evt) = $self->fetch_callnumber($copy->call_number);
1006         return (undef,$evt) if $evt;
1007         return ($cn->owning_lib);
1008 }
1009
1010 sub fetch_copy_note {
1011         my( $self, $id ) = @_;
1012         my( $note, $evt );
1013         $logger->debug("Fetching copy note $id");
1014         $note = $self->cstorereq(
1015                 'open-ils.cstore.direct.asset.copy_note.retrieve', $id );
1016         $evt = OpenILS::Event->new('ASSET_COPY_NOTE_NOT_FOUND', id => $id ) unless $note;
1017         return ($note, $evt);
1018 }
1019
1020 sub fetch_call_numbers_by_title {
1021         my( $self, $titleid ) = @_;
1022         $logger->info("Fetching call numbers by title $titleid");
1023         return $self->cstorereq(
1024                 'open-ils.cstore.direct.asset.call_number.search.atomic', 
1025                 { record => $titleid, deleted => 'f' });
1026                 #'open-ils.storage.direct.asset.call_number.search.record.atomic', $titleid);
1027 }
1028
1029 sub fetch_copies_by_call_number {
1030         my( $self, $cnid ) = @_;
1031         $logger->info("Fetching copies by call number $cnid");
1032         return $self->cstorereq(
1033                 'open-ils.cstore.direct.asset.copy.search.atomic', { call_number => $cnid, deleted => 'f' } );
1034                 #'open-ils.storage.direct.asset.copy.search.call_number.atomic', $cnid );
1035 }
1036
1037 sub fetch_user_by_barcode {
1038         my( $self, $bc ) = @_;
1039         my $cardid = $self->cstorereq(
1040                 'open-ils.cstore.direct.actor.card.id_list', { barcode => $bc } );
1041         return (undef, OpenILS::Event->new('ACTOR_CARD_NOT_FOUND', barcode => $bc)) unless $cardid;
1042         my $user = $self->cstorereq(
1043                 'open-ils.cstore.direct.actor.user.search', { card => $cardid } );
1044         return (undef, OpenILS::Event->new('ACTOR_USER_NOT_FOUND', card => $cardid)) unless $user;
1045         return ($user);
1046         
1047 }
1048
1049
1050 # ---------------------------------------------------------------------
1051 # Updates and returns the patron penalties
1052 # ---------------------------------------------------------------------
1053 sub update_patron_penalties {
1054         my( $self, %args ) = @_;
1055         return $self->simplereq(
1056                 'open-ils.penalty',
1057                 'open-ils.penalty.patron_penalty.calculate', 
1058                 { update => 1, %args }
1059         );
1060 }
1061
1062 sub fetch_bill {
1063         my( $self, $billid ) = @_;
1064         $logger->debug("Fetching billing $billid");
1065         my $bill = $self->cstorereq(
1066                 'open-ils.cstore.direct.money.billing.retrieve', $billid );
1067         my $evt = OpenILS::Event->new('MONEY_BILLING_NOT_FOUND') unless $bill;
1068         return($bill, $evt);
1069 }
1070
1071
1072
1073 my $ORG_TREE;
1074 sub fetch_org_tree {
1075         my $self = shift;
1076         return $ORG_TREE if $ORG_TREE;
1077         return $ORG_TREE = OpenILS::Utils::CStoreEditor->new->search_actor_org_unit( 
1078                 [
1079                         {"parent_ou" => undef },
1080                         {
1081                                 flesh                           => 2,
1082                                 flesh_fields    => { aou =>  ['children'] },
1083                                 order_by       => { aou => 'name'}
1084                         }
1085                 ]
1086         )->[0];
1087 }
1088
1089 sub walk_org_tree {
1090         my( $self, $node, $callback ) = @_;
1091         return unless $node;
1092         $callback->($node);
1093         if( $node->children ) {
1094                 $self->walk_org_tree($_, $callback) for @{$node->children};
1095         }
1096 }
1097
1098 sub is_true {
1099         my( $self, $item ) = @_;
1100         return 1 if $item and $item !~ /^f$/i;
1101         return 0;
1102 }
1103
1104
1105 # This logic now lives in storage
1106 sub __patron_money_owed {
1107         my( $self, $patronid ) = @_;
1108         my $ses = OpenSRF::AppSession->create('open-ils.storage');
1109         my $req = $ses->request(
1110                 'open-ils.storage.money.billable_transaction.summary.search',
1111                 { usr => $patronid, xact_finish => undef } );
1112
1113         my $total = 0;
1114         my $data;
1115         while( $data = $req->recv ) {
1116                 $data = $data->content;
1117                 $total += $data->balance_owed;
1118         }
1119         return $total;
1120 }
1121
1122 sub patron_money_owed {
1123         my( $self, $userid ) = @_;
1124         return $self->storagereq(
1125                 'open-ils.storage.actor.user.total_owed', $userid);
1126 }
1127
1128 sub patron_total_items_out {
1129         my( $self, $userid ) = @_;
1130         return $self->storagereq(
1131                 'open-ils.storage.actor.user.total_out', $userid);
1132 }
1133
1134
1135
1136
1137 #---------------------------------------------------------------------
1138 # Returns  ($summary, $event) 
1139 #---------------------------------------------------------------------
1140 sub fetch_mbts {
1141         my $self = shift;
1142         my $id  = shift;
1143         my $editor = shift || OpenILS::Utils::CStoreEditor->new;
1144
1145         $id = $id->id if (ref($id));
1146
1147         my $xact = $editor->retrieve_money_billable_transaction(
1148                 [
1149                         $id, {  
1150                                 flesh => 1, 
1151                                 flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] } 
1152                         }
1153                 ]
1154         ) or return (undef, $editor->event);
1155
1156         return $self->make_mbts($xact);
1157 }
1158
1159
1160 #---------------------------------------------------------------------
1161 # Given a list of money.billable_transaction objects, this creates
1162 # transaction summary objects for each
1163 #--------------------------------------------------------------------
1164 sub make_mbts {
1165         my $self = shift;
1166         my @xacts = @_;
1167
1168         my @mbts;
1169         for my $x (@xacts) {
1170
1171                 my $s = new Fieldmapper::money::billable_transaction_summary;
1172
1173                 $s->id( $x->id );
1174                 $s->usr( $x->usr );
1175                 $s->xact_start( $x->xact_start );
1176                 $s->xact_finish( $x->xact_finish );
1177                 
1178                 my $to = 0;
1179                 my $lb = undef;
1180                 for my $b (@{ $x->billings }) {
1181                         next if ($self->is_true($b->voided));
1182                         $to += ($b->amount * 100);
1183                         $lb ||= $b->billing_ts;
1184                         if ($b->billing_ts ge $lb) {
1185                                 $lb = $b->billing_ts;
1186                                 $s->last_billing_note($b->note);
1187                                 $s->last_billing_ts($b->billing_ts);
1188                                 $s->last_billing_type($b->billing_type);
1189                         }
1190                 }
1191
1192                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
1193                 
1194                 my $tp = 0;
1195                 my $lp = undef;
1196                 for my $p (@{ $x->payments }) {
1197                         next if ($self->is_true($p->voided));
1198                         $tp += ($p->amount * 100);
1199                         $lp ||= $p->payment_ts;
1200                         if ($p->payment_ts ge $lp) {
1201                                 $lp = $p->payment_ts;
1202                                 $s->last_payment_note($p->note);
1203                                 $s->last_payment_ts($p->payment_ts);
1204                                 $s->last_payment_type($p->payment_type);
1205                         }
1206                 }
1207
1208                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
1209                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
1210                 $s->xact_type('grocery') if ($x->grocery);
1211                 $s->xact_type('circulation') if ($x->circulation);
1212
1213                 $logger->debug("Created mbts with balance_owed = ". $s->balance_owed);
1214                 
1215                 push @mbts, $s;
1216         }
1217                 
1218         return @mbts;
1219 }
1220                 
1221                 
1222 sub ou_ancestor_setting_value {
1223     my $obj = ou_ancestor_setting(@_);
1224     return ($obj) ? $obj->{value} : undef;
1225 }
1226
1227 sub ou_ancestor_setting {
1228     my( $self, $orgid, $name, $e ) = @_;
1229     $e = $e || OpenILS::Utils::CStoreEditor->new;
1230
1231     do {
1232         my $setting = $e->search_actor_org_unit_setting({org_unit=>$orgid, name=>$name})->[0];
1233
1234         if( $setting ) {
1235             $logger->info("found org_setting $name at org $orgid : " . $setting->value);
1236             return { org => $orgid, value => OpenSRF::Utils::JSON->JSON2perl($setting->value) };
1237         }
1238
1239         my $org = $e->retrieve_actor_org_unit($orgid) or return $e->event;
1240         $orgid = $org->parent_ou or return undef;
1241
1242     } while(1);
1243
1244     return undef;
1245 }       
1246                 
1247
1248 # returns the ISO8601 string representation of the requested epoch in GMT
1249 sub epoch2ISO8601 {
1250     my( $self, $epoch ) = @_;
1251     my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($epoch);
1252     $year += 1900; $mon += 1;
1253     my $date = sprintf(
1254         '%s-%0.2d-%0.2dT%0.2d:%0.2d:%0.2d-00',
1255         $year, $mon, $mday, $hour, $min, $sec);
1256     return $date;
1257 }
1258                         
1259         
1260 1;
1261