8125f4a13f64c6c1673b7d5dbaebc97a29d44064
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Actor.pm
1 package OpenILS::Application::Actor;
2 use base qw/OpenSRF::Application/;
3 use strict; use warnings;
4 use Data::Dumper;
5 $Data::Dumper::Indent = 0;
6 use OpenILS::Event;
7
8 use Digest::MD5 qw(md5_hex);
9
10 use OpenSRF::EX qw(:try);
11 use OpenILS::EX;
12 use OpenILS::Perm;
13
14 use OpenILS::Application::AppUtils;
15
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Application::Search::Actor;
18 use OpenILS::Utils::ModsParser;
19 use OpenSRF::Utils::Logger qw/$logger/;
20 use OpenSRF::Utils qw/:datetime/;
21
22 use OpenSRF::Utils::Cache;
23
24 use DateTime;
25 use DateTime::Format::ISO8601;
26
27 use OpenILS::Application::Actor::Container;
28
29 sub initialize {
30         OpenILS::Application::Actor::Container->initialize();
31 }
32
33 my $apputils = "OpenILS::Application::AppUtils";
34 my $U = $apputils;
35
36 sub _d { warn "Patron:\n" . Dumper(shift()); }
37
38 my $cache_client;
39
40
41 my $set_user_settings;
42 my $set_ou_settings;
43
44 __PACKAGE__->register_method(
45         method  => "set_user_settings",
46         api_name        => "open-ils.actor.patron.settings.update",
47 );
48 sub set_user_settings {
49         my( $self, $client, $user_session, $uid, $settings ) = @_;
50         
51         $logger->debug("Setting user settings: $user_session, $uid, " . Dumper($settings));
52
53         my( $staff, $user, $evt ) = 
54                 $apputils->checkses_requestor( $user_session, $uid, 'UPDATE_USER' );    
55         return $evt if $evt;
56         
57
58         my ($params) = map { 
59                 [{ usr => $user->id, name => $_}, {value => $$settings{$_}}] } keys %$settings;
60
61         $logger->activity("User " . $staff->id . " updating user $uid settings with: " . Dumper($params));
62
63         return $apputils->simplereq(
64                 'open-ils.storage',
65                 'open-ils.storage.direct.actor.user_setting.batch.merge', $params );
66                 
67 }
68
69
70
71 __PACKAGE__->register_method(
72         method  => "set_ou_settings",
73         api_name        => "open-ils.actor.org_unit.settings.update",
74 );
75 sub set_ou_settings {
76         my( $self, $client, $user_session, $ouid, $settings ) = @_;
77         
78         my( $staff, $evt ) = $apputils->checkses( $user_session );
79         return $evt if $evt;
80         $evt = $apputils->check_perms( $staff->id, $ouid, 'UPDATE_ORG_UNIT' );
81         return $evt if $evt;
82
83
84         my ($params) = 
85                 map { [{ org_unit => $ouid, name => $_}, {value => $$settings{$_}}] } keys %$settings;
86
87         $logger->activity("Updating org unit [$ouid] settings with: " . Dumper($params));
88
89         return $apputils->simplereq(
90                 'open-ils.storage',
91                 'open-ils.storage.direct.actor.org_unit_setting.merge', @$params );
92 }
93
94
95 my $fetch_user_settings;
96 my $fetch_ou_settings;
97
98 __PACKAGE__->register_method(
99         method  => "user_settings",
100         api_name        => "open-ils.actor.patron.settings.retrieve",
101 );
102 sub user_settings {
103         my( $self, $client, $user_session, $uid ) = @_;
104         
105         my( $staff, $user, $evt ) = 
106                 $apputils->checkses_requestor( $user_session, $uid, 'VIEW_USER' );
107         return $evt if $evt;
108
109         $logger->debug("User " . $staff->id . " fetching user $uid\n");
110         my $s = $apputils->simplereq(
111                 'open-ils.storage',
112                 'open-ils.storage.direct.actor.user_setting.search.usr.atomic',$uid );
113
114         return { map { ($_->name,$_->value) } @$s };
115 }
116
117
118
119 __PACKAGE__->register_method(
120         method  => "ou_settings",
121         api_name        => "open-ils.actor.org_unit.settings.retrieve",
122 );
123 sub ou_settings {
124         my( $self, $client, $ouid ) = @_;
125         
126         my $s = $apputils->simplereq(
127                 'open-ils.storage',
128                 'open-ils.storage.direct.actor.org_unit_setting.search.org_unit.atomic', $ouid);
129
130         return { map { ($_->name,$_->value) } @$s };
131 }
132
133
134
135 __PACKAGE__->register_method(
136         method  => "update_patron",
137         api_name        => "open-ils.actor.patron.update",);
138
139 sub update_patron {
140         my( $self, $client, $user_session, $patron ) = @_;
141
142         my $session = $apputils->start_db_session();
143         my $err = undef;
144
145         $logger->info("Creating new patron...") if $patron->isnew; 
146         $logger->info("Updating Patron: " . $patron->id) unless $patron->isnew;
147
148         my( $user_obj, $evt ) = $U->checkses($user_session);
149         return $evt if $evt;
150
151         # XXX does this user have permission to add/create users.  Granularity?
152         # $new_patron is the patron in progress.  $patron is the original patron
153         # passed in with the method.  new_patron will change as the components
154         # of patron are added/updated.
155
156         my $new_patron;
157
158         # unflesh the real items on the patron
159         $patron->card( $patron->card->id ) if(ref($patron->card));
160         $patron->billing_address( $patron->billing_address->id ) 
161                 if(ref($patron->billing_address));
162         $patron->mailing_address( $patron->mailing_address->id ) 
163                 if(ref($patron->mailing_address));
164
165         # create/update the patron first so we can use his id
166         if($patron->isnew()) {
167                 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
168                 return $evt if $evt;
169         } else { $new_patron = $patron; }
170
171         ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
172         return $evt if $evt;
173
174         ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
175         return $evt if $evt;
176
177         ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
178         return $evt if $evt;
179
180         # re-update the patron if anything has happened to him during this process
181         if($new_patron->ischanged()) {
182                 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
183                 return $evt if $evt;
184         }
185
186         #$session = OpenSRF::AppSession->create("open-ils.storage");  # why did i put this here?
187
188         ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
189         return $evt if $evt;
190
191         ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
192         return $evt if $evt;
193
194         ($new_patron, $evt) = _create_standing_penalties($session, $user_session, $patron, $new_patron, $user_obj);
195         return $evt if $evt;
196
197         $apputils->commit_db_session($session);
198
199         #warn "Patron Update/Create complete\n";
200         return flesh_user($new_patron->id());
201 }
202
203
204
205
206 __PACKAGE__->register_method(
207         method  => "user_retrieve_fleshed_by_id",
208         api_name        => "open-ils.actor.user.fleshed.retrieve",);
209
210 sub user_retrieve_fleshed_by_id {
211         my( $self, $client, $user_session, $user_id ) = @_;
212
213         my( $requestor, $target, $evt ) = $apputils->
214                 checkses_requestor( $user_session, $user_id, 'VIEW_USER' );
215         return $evt if $evt;
216
217         return flesh_user($user_id);
218 }
219
220
221 sub flesh_user {
222         my $id = shift;
223         my $session = shift;
224
225         my $kill = 0;
226
227         if(!$session) {
228                 $session = OpenSRF::AppSession->create("open-ils.storage");
229                 $kill = 1;
230         }
231
232         # grab the user with the given id 
233         my $ureq = $session->request(
234                         "open-ils.storage.direct.actor.user.retrieve", $id);
235         my $user = $ureq->gather(1);
236
237         if(!$user) { return undef; }
238
239         # grab the cards
240         my $cards_req = $session->request(
241                         "open-ils.storage.direct.actor.card.search.usr.atomic",
242                         $user->id() );
243         $user->cards( $cards_req->gather(1) );
244
245         for my $c(@{$user->cards}) {
246                 if($c->id == $user->card || $c->id eq $user->card ) {
247                         #warn "Setting my card to " . $c->id . "\n";
248                         $user->card($c);
249                 }
250         }
251
252         my $add_req = $session->request(
253                         "open-ils.storage.direct.actor.user_address.search.usr.atomic",
254                         $user->id() );
255         $user->addresses( $add_req->gather(1) );
256
257         for my $c(@{$user->addresses}) {
258                 if($c->id eq $user->billing_address ) { $user->billing_address($c); }
259                 if($c->id eq $user->mailing_address ) { $user->mailing_address($c); }
260         }
261
262         my $stat_req = $session->request(
263                 "open-ils.storage.direct.actor.stat_cat_entry_user_map.search.target_usr.atomic",
264                 $user->id() );
265         $user->stat_cat_entries($stat_req->gather(1));
266
267         my $standing_penalties_req = $session->request(
268                 "open-ils.storage.direct.actor.user_standing_penalty.search.usr.atomic",
269                 $user->id() );
270         $user->standing_penalties($standing_penalties_req->gather(1));
271
272         if($kill) { $session->disconnect(); }
273         $user->clear_passwd();
274
275         return $user;
276 }
277
278
279 # clone and clear stuff that would break the database
280 sub _clone_patron {
281         my $patron = shift;
282
283         my $new_patron = $patron->clone;
284
285         # Using the Fieldmapper clone method
286         #my $new_patron = Fieldmapper::actor::user->new();
287
288         #my $fmap = $Fieldmapper::fieldmap;
289         #no strict; # shallow clone, may be useful in the fieldmapper
290         #for my $field 
291         #       (keys %{$fmap->{"Fieldmapper::actor::user"}->{'fields'}}) {
292         #               $new_patron->$field( $patron->$field() );
293         #}
294         #use strict;
295
296         # clear these
297         $new_patron->clear_billing_address();
298         $new_patron->clear_mailing_address();
299         $new_patron->clear_addresses();
300         $new_patron->clear_card();
301         $new_patron->clear_cards();
302         $new_patron->clear_id();
303         $new_patron->clear_isnew();
304         $new_patron->clear_ischanged();
305         $new_patron->clear_isdeleted();
306         $new_patron->clear_stat_cat_entries();
307         $new_patron->clear_permissions();
308         $new_patron->clear_standing_penalties();
309
310         return $new_patron;
311 }
312
313
314 sub _add_patron {
315
316         my $session             = shift;
317         my $patron              = shift;
318         my $user_obj    = shift;
319
320         my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
321         return (undef, $evt) if $evt;
322
323         $logger->info("Creating new user in the DB with username: ".$patron->usrname());
324
325         my $id = $session->request(
326                 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
327         return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
328
329         $logger->info("Successfully created new user [$id] in DB");
330
331         return ( $session->request( 
332                 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
333 }
334
335
336 sub _update_patron {
337         my( $session, $patron, $user_obj) = @_;
338
339         $logger->info("Updating patron ".$patron->id." in DB");
340         my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
341         return (undef, $evt) if $evt;
342
343         $patron->clear_passwd if ($patron->passwd =~ /^\s+/);
344
345         my $stat = $session->request(
346                 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
347         return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
348
349         return ($patron);
350 }
351
352
353 sub _add_update_addresses {
354
355         my $session = shift;
356         my $patron = shift;
357         my $new_patron = shift;
358
359         my $evt;
360
361         my $current_id; # id of the address before creation
362
363         for my $address (@{$patron->addresses()}) {
364
365                 $address->usr($new_patron->id());
366
367                 if(ref($address) and $address->isnew()) {
368
369                         $current_id = $address->id();
370                         ($address, $evt) = _add_address($session,$address);
371                         return (undef, $evt) if $evt;
372
373                         if( $patron->billing_address() and 
374                                         $patron->billing_address() == $current_id ) {
375                                 $new_patron->billing_address($address->id());
376                                 $new_patron->ischanged(1);
377                         }
378
379                         if( $patron->mailing_address() and
380                                         $patron->mailing_address() == $current_id ) {
381                                 $new_patron->mailing_address($address->id());
382                                 $new_patron->ischanged(1);
383                         }
384
385                 } elsif( ref($address) and $address->ischanged() ) {
386
387                         $address->usr($new_patron->id());
388                         ($address, $evt) = _update_address($session, $address);
389                         return (undef, $evt) if $evt;
390
391                 } elsif( ref($address) and $address->isdeleted() ) {
392
393                         if( $address->id() == $new_patron->mailing_address() ) {
394                                 $new_patron->clear_mailing_address();
395                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
396                                 return (undef, $evt) if $evt;
397                         }
398
399                         if( $address->id() == $new_patron->billing_address() ) {
400                                 $new_patron->clear_billing_address();
401                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
402                                 return (undef, $evt) if $evt;
403                         }
404
405                         $evt = _delete_address($session, $address);
406                         return (undef, $evt) if $evt;
407                 }
408         }
409
410         return ( $new_patron, undef );
411 }
412
413
414 # adds an address to the db and returns the address with new id
415 sub _add_address {
416         my($session, $address) = @_;
417         $address->clear_id();
418
419         $logger->info("Creating new address at street ".$address->street1);
420
421         # put the address into the database
422         my $id = $session->request(
423                 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
424         return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
425
426         $address->id( $id );
427         return ($address, undef);
428 }
429
430
431 sub _update_address {
432         my( $session, $address ) = @_;
433         $logger->info("Updating address ".$address->id." in the DB");
434
435         my $stat = $session->request(
436                 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
437
438         return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
439         return ($address, undef);
440 }
441
442
443
444 sub _add_update_cards {
445
446         my $session = shift;
447         my $patron = shift;
448         my $new_patron = shift;
449
450         my $evt;
451
452         my $virtual_id; #id of the card before creation
453         for my $card (@{$patron->cards()}) {
454
455                 $card->usr($new_patron->id());
456
457                 if(ref($card) and $card->isnew()) {
458
459                         $virtual_id = $card->id();
460                         ( $card, $evt ) = _add_card($session,$card);
461                         return (undef, $evt) if $evt;
462
463                         #if(ref($patron->card)) { $patron->card($patron->card->id); }
464                         if($patron->card() == $virtual_id) {
465                                 $new_patron->card($card->id());
466                                 $new_patron->ischanged(1);
467                         }
468
469                 } elsif( ref($card) and $card->ischanged() ) {
470                         $card->usr($new_patron->id());
471                         $evt = _update_card($session, $card);
472                         return (undef, $evt) if $evt;
473                 }
474         }
475
476         return ( $new_patron, undef );
477 }
478
479
480 # adds an card to the db and returns the card with new id
481 sub _add_card {
482         my( $session, $card ) = @_;
483         $card->clear_id();
484         $logger->info("Adding new patron card ".$card->barcode);
485
486         my $id = $session->request(
487                 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
488         return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
489         $logger->info("Successfully created patron card $id");
490
491         $card->id($id);
492         return ( $card, undef );
493 }
494
495
496 # returns event on error.  returns undef otherwise
497 sub _update_card {
498         my( $session, $card ) = @_;
499         $logger->info("Updating patron card ".$card->id);
500
501         my $stat = $session->request(
502                 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
503         return $U->DB_UPDATE_FAILED($card) unless defined($stat);
504         return undef;
505 }
506
507
508
509
510 # returns event on error.  returns undef otherwise
511 sub _delete_address {
512         my( $session, $address ) = @_;
513         $logger->info("Deleting address ".$address->id." from DB");
514
515         my $stat = $session->request(
516                 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
517
518         return $U->DB_UPDATE_FAILED($address) unless defined($stat);
519         return undef;
520 }
521
522
523
524 sub _add_survey_responses {
525         my ($session, $patron, $new_patron) = @_;
526
527         $logger->info( "Updating survey responses for patron ".$new_patron->id );
528
529         my $responses = $patron->survey_responses;
530
531         if($responses) {
532
533                 $_->usr($new_patron->id) for (@$responses);
534
535                 my $evt = $U->simplereq( "open-ils.circ", 
536                         "open-ils.circ.survey.submit.user_id", $responses );
537
538                 return (undef, $evt) if defined($U->event_code($evt));
539
540         }
541
542         return ( $new_patron, undef );
543 }
544
545
546 sub _create_stat_maps {
547
548         my($session, $user_session, $patron, $new_patron) = @_;
549
550         my $maps = $patron->stat_cat_entries();
551
552         for my $map (@$maps) {
553
554                 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
555
556                 if ($map->isdeleted()) {
557                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
558
559                 } elsif ($map->isnew()) {
560                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
561                         $map->clear_id;
562                 }
563
564
565                 $map->target_usr($new_patron->id);
566
567                 #warn "
568                 $logger->info("Updating stat entry with method $method and map $map");
569
570                 my $stat = $session->request($method, $map)->gather(1);
571                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
572
573         }
574
575         return ($new_patron, undef);
576 }
577
578 sub _create_perm_maps {
579
580         my($session, $user_session, $patron, $new_patron) = @_;
581
582         my $maps = $patron->permissions;
583
584         for my $map (@$maps) {
585
586                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
587                 if ($map->isdeleted()) {
588                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
589                 } elsif ($map->isnew()) {
590                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
591                         $map->clear_id;
592                 }
593
594
595                 $map->usr($new_patron->id);
596
597                 #warn( "Updating permissions with method $method and session $user_session and map $map" );
598                 $logger->info( "Updating permissions with method $method and map $map" );
599
600                 my $stat = $session->request($method, $map)->gather(1);
601                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
602
603         }
604
605         return ($new_patron, undef);
606 }
607
608
609 sub _create_standing_penalties {
610
611         my($session, $user_session, $patron, $new_patron) = @_;
612
613         my $maps = $patron->standing_penalties;
614         my $method;
615
616         for my $map (@$maps) {
617
618                 if ($map->isdeleted()) {
619                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
620                 } elsif ($map->isnew()) {
621                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
622                         $map->clear_id;
623                 } else {
624                         next;
625                 }
626
627                 $map->usr($new_patron->id);
628
629                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
630
631                 my $stat = $session->request($method, $map)->gather(1);
632                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
633         }
634
635         return ($new_patron, undef);
636 }
637
638
639
640 __PACKAGE__->register_method(
641         method  => "search_username",
642         api_name        => "open-ils.actor.user.search.username",
643 );
644
645 sub search_username {
646         my($self, $client, $username) = @_;
647         my $users = OpenILS::Application::AppUtils->simple_scalar_request(
648                         "open-ils.storage", 
649                         "open-ils.storage.direct.actor.user.search.usrname.atomic",
650                         $username );
651         return $users;
652 }
653
654
655
656
657 __PACKAGE__->register_method(
658         method  => "user_retrieve_by_barcode",
659         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
660
661 sub user_retrieve_by_barcode {
662         my($self, $client, $user_session, $barcode) = @_;
663
664         $logger->debug("Searching for user with barcode $barcode");
665         my ($user_obj, $evt) = $apputils->checkses($user_session);
666         return $evt if $evt;
667
668         my $session = OpenSRF::AppSession->create("open-ils.storage");
669
670         # find the card with the given barcode
671         my $creq        = $session->request(
672                         "open-ils.storage.direct.actor.card.search.barcode.atomic",
673                         $barcode );
674         my $card = $creq->gather(1);
675
676         if(!$card || !$card->[0]) {
677                 $session->disconnect();
678                 return OpenILS::Event->new( 'USER_NOT_FOUND' );
679         }
680
681         $card = $card->[0];
682         my $user = flesh_user($card->usr(), $session);
683         $session->disconnect();
684         if(!$user) { return OpenILS::Event->new( 'USER_NOT_FOUND' ); }
685         return $user;
686
687 }
688
689
690
691 __PACKAGE__->register_method(
692         method  => "get_user_by_id",
693         api_name        => "open-ils.actor.user.retrieve",);
694
695 sub get_user_by_id {
696         my ($self, $client, $user_session, $id) = @_;
697
698         my $user_obj = $apputils->check_user_session( $user_session ); 
699
700         return $apputils->simple_scalar_request(
701                 "open-ils.storage",
702                 "open-ils.storage.direct.actor.user.retrieve",
703                 $id );
704 }
705
706
707
708 __PACKAGE__->register_method(
709         method  => "get_org_types",
710         api_name        => "open-ils.actor.org_types.retrieve",);
711
712 my $org_types;
713 sub get_org_types {
714         my($self, $client) = @_;
715
716         return $org_types if $org_types;
717          return $org_types = 
718                  $apputils->simple_scalar_request(
719                         "open-ils.storage",
720                         "open-ils.storage.direct.actor.org_unit_type.retrieve.all.atomic" );
721 }
722
723
724
725 __PACKAGE__->register_method(
726         method  => "get_user_profiles",
727         api_name        => "open-ils.actor.user.profiles.retrieve",
728 );
729
730 my $user_profiles;
731 sub get_user_profiles {
732         return $user_profiles if $user_profiles;
733
734         return $user_profiles = 
735                 $apputils->simple_scalar_request(
736                         "open-ils.storage",
737                         "open-ils.storage.direct.actor.profile.retrieve.all.atomic");
738 }
739
740
741
742 __PACKAGE__->register_method(
743         method  => "get_user_ident_types",
744         api_name        => "open-ils.actor.user.ident_types.retrieve",
745 );
746 my $ident_types;
747 sub get_user_ident_types {
748         return $ident_types if $ident_types;
749         return $ident_types = 
750                 $apputils->simple_scalar_request(
751                 "open-ils.storage",
752                 "open-ils.storage.direct.config.identification_type.retrieve.all.atomic" );
753 }
754
755
756
757
758 __PACKAGE__->register_method(
759         method  => "get_org_unit",
760         api_name        => "open-ils.actor.org_unit.retrieve",
761 );
762
763 sub get_org_unit {
764
765         my( $self, $client, $user_session, $org_id ) = @_;
766
767         if(defined($user_session) && !defined($org_id)) {
768                 my $user_obj = 
769                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
770                 if(!defined($org_id)) {
771                         $org_id = $user_obj->home_ou;
772                 }
773         }
774
775
776         my $home_ou = OpenILS::Application::AppUtils->simple_scalar_request(
777                 "open-ils.storage",
778                 "open-ils.storage.direct.actor.org_unit.retrieve", 
779                 $org_id );
780
781         return $home_ou;
782 }
783
784
785 # build the org tree
786
787 __PACKAGE__->register_method(
788         method  => "get_org_tree",
789         api_name        => "open-ils.actor.org_tree.retrieve",
790         argc            => 0, 
791         note            => "Returns the entire org tree structure",
792 );
793
794 sub get_org_tree {
795         my( $self, $client) = @_;
796
797         if(!$cache_client) {
798                 $cache_client = OpenSRF::Utils::Cache->new("global", 0);
799         }
800         # see if it's in the cache
801         #warn "Getting ORG Tree\n";
802         my $tree = $cache_client->get_cache('orgtree');
803         if($tree) { 
804                 #warn "Found orgtree in cache. returning...\n";
805                 return $tree; 
806         }
807
808         my $orglist = $apputils->simple_scalar_request( 
809                 "open-ils.storage", 
810                 "open-ils.storage.direct.actor.org_unit.retrieve.all.atomic" );
811
812         #if($orglist) {
813                 #warn "found org list\n";
814         #}
815
816         $tree = $self->build_org_tree($orglist);
817         $cache_client->put_cache('orgtree', $tree);
818
819         return $tree;
820
821 }
822
823 # turns an org list into an org tree
824 sub build_org_tree {
825
826         my( $self, $orglist) = @_;
827
828         return $orglist unless ( 
829                         ref($orglist) and @$orglist > 1 );
830
831         my @list = sort { 
832                 $a->ou_type <=> $b->ou_type ||
833                 $a->name cmp $b->name } @$orglist;
834
835         for my $org (@list) {
836
837                 next unless ($org and defined($org->parent_ou));
838                 my ($parent) = grep { $_->id == $org->parent_ou } @list;
839                 next unless $parent;
840
841                 $parent->children([]) unless defined($parent->children); 
842                 push( @{$parent->children}, $org );
843         }
844
845         return $list[0];
846
847 }
848
849
850 __PACKAGE__->register_method(
851         method  => "get_org_descendants",
852         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
853 );
854
855 # depth is optional.  org_unit is the id
856 sub get_org_descendants {
857         my( $self, $client, $org_unit, $depth ) = @_;
858         my $orglist = $apputils->simple_scalar_request(
859                         "open-ils.storage", 
860                         "open-ils.storage.actor.org_unit.descendants.atomic",
861                         $org_unit, $depth );
862         return $self->build_org_tree($orglist);
863 }
864
865
866 __PACKAGE__->register_method(
867         method  => "get_org_ancestors",
868         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
869 );
870
871 # depth is optional.  org_unit is the id
872 sub get_org_ancestors {
873         my( $self, $client, $org_unit, $depth ) = @_;
874         my $orglist = $apputils->simple_scalar_request(
875                         "open-ils.storage", 
876                         "open-ils.storage.actor.org_unit.ancestors.atomic",
877                         $org_unit, $depth );
878         return $self->build_org_tree($orglist);
879 }
880
881
882 __PACKAGE__->register_method(
883         method  => "get_standings",
884         api_name        => "open-ils.actor.standings.retrieve"
885 );
886
887 my $user_standings;
888 sub get_standings {
889         return $user_standings if $user_standings;
890         return $user_standings = 
891                 $apputils->simple_scalar_request(
892                         "open-ils.storage",
893                         "open-ils.storage.direct.config.standing.retrieve.all.atomic" );
894 }
895
896
897
898 __PACKAGE__->register_method(
899         method  => "get_my_org_path",
900         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
901 );
902
903 sub get_my_org_path {
904         my( $self, $client, $user_session, $org_id ) = @_;
905         my $user_obj = $apputils->check_user_session($user_session); 
906         if(!defined($org_id)) { $org_id = $user_obj->home_ou; }
907
908         return $apputils->simple_scalar_request(
909                 "open-ils.storage",
910                 "open-ils.storage.actor.org_unit.full_path.atomic",
911                 $org_id );
912 }
913
914
915 __PACKAGE__->register_method(
916         method  => "patron_adv_search",
917         api_name        => "open-ils.actor.patron.search.advanced" );
918
919 sub patron_adv_search {
920         my( $self, $client, $staff_login, $search_hash ) = @_;
921
922         #warn "patron adv with $staff_login and search " . 
923                 #Dumper($search_hash) . "\n";
924
925         my $session = OpenSRF::AppSession->create("open-ils.storage");
926         my $req = $session->request(
927                 "open-ils.storage.actor.user.crazy_search", $search_hash);
928
929         my $ans = $req->gather(1);
930
931         my %hash = map { ($_ =>1) } @$ans;
932         $ans = [ keys %hash ];
933
934         #warn "Returning @$ans\n";
935
936         $session->disconnect();
937         return $ans;
938
939 }
940
941
942
943 sub _verify_password {
944         my($user_session, $password) = @_;
945         my $user_obj = $apputils->check_user_session($user_session); 
946
947         #grab the user with password
948         $user_obj = $apputils->simple_scalar_request(
949                 "open-ils.storage", 
950                 "open-ils.storage.direct.actor.user.retrieve",
951                 $user_obj->id );
952
953         if($user_obj->passwd eq $password) {
954                 return 1;
955         }
956
957         return 0;
958 }
959
960
961 __PACKAGE__->register_method(
962         method  => "update_password",
963         api_name        => "open-ils.actor.user.password.update");
964
965 __PACKAGE__->register_method(
966         method  => "update_password",
967         api_name        => "open-ils.actor.user.username.update");
968
969 __PACKAGE__->register_method(
970         method  => "update_password",
971         api_name        => "open-ils.actor.user.email.update");
972
973 sub update_password {
974         my( $self, $client, $user_session, $new_value, $current_password ) = @_;
975
976         my $evt;
977
978         #warn "Updating user with method " .$self->api_name . "\n";
979         my $user_obj = $apputils->check_user_session($user_session); 
980
981         if($self->api_name =~ /password/o) {
982
983                 #make sure they know the current password
984                 if(!_verify_password($user_session, md5_hex($current_password))) {
985                         return OpenILS::EX->new("USER_WRONG_PASSWORD")->ex;
986                 }
987
988                 $user_obj->passwd($new_value);
989         } 
990         elsif($self->api_name =~ /username/o) {
991                 my $users = search_username(undef, undef, $new_value); 
992                 if( $users and $users->[0] ) {
993                         return OpenILS::Event->new('USERNAME_EXISTS');
994                 }
995                 $user_obj->usrname($new_value);
996         }
997
998         elsif($self->api_name =~ /email/o) {
999                 #warn "Updating email to $new_value\n";
1000                 $user_obj->email($new_value);
1001         }
1002
1003         my $session = $apputils->start_db_session();
1004
1005         ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj);
1006         return $evt if $evt;
1007
1008         $apputils->commit_db_session($session);
1009
1010         if($user_obj) { return 1; }
1011         return undef;
1012 }
1013
1014
1015 __PACKAGE__->register_method(
1016         method  => "check_user_perms",
1017         api_name        => "open-ils.actor.user.perm.check",
1018         notes           => <<"  NOTES");
1019         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1020         perm type, if the user does *not* have the given permission it is added
1021         to a list which is returned from the method.  If all permissions
1022         are allowed, an empty list is returned
1023         if the logged in user does not match 'user_id', then the logged in user must
1024         have VIEW_PERMISSION priveleges.
1025         NOTES
1026
1027 sub check_user_perms {
1028         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1029
1030         my( $staff, $evt ) = $apputils->checkses($login_session);
1031         return $evt if $evt;
1032
1033         if($staff->id ne $user_id) {
1034                 if( my $evt = $apputils->check_perms(
1035                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1036                         return $evt;
1037                 }
1038         }
1039
1040         my @not_allowed;
1041         for my $perm (@$perm_types) {
1042                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1043                         push @not_allowed, $perm;
1044                 }
1045         }
1046
1047         return \@not_allowed
1048 }
1049
1050 __PACKAGE__->register_method(
1051         method  => "check_user_perms2",
1052         api_name        => "open-ils.actor.user.perm.check.multi_org",
1053         notes           => q/
1054                 Checks the permissions on a list of perms and orgs for a user
1055                 @param authtoken The login session key
1056                 @param user_id The id of the user to check
1057                 @param orgs The array of org ids
1058                 @param perms The array of permission names
1059                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1060                 if the logged in user does not match 'user_id', then the logged in user must
1061                 have VIEW_PERMISSION priveleges.
1062         /);
1063
1064 sub check_user_perms2 {
1065         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1066
1067         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1068                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1069         return $evt if $evt;
1070
1071         my @not_allowed;
1072         for my $org (@$orgs) {
1073                 for my $perm (@$perms) {
1074                         if($apputils->check_perms($user_id, $org, $perm)) {
1075                                 push @not_allowed, [ $org, $perm ];
1076                         }
1077                 }
1078         }
1079
1080         return \@not_allowed
1081 }
1082
1083
1084 __PACKAGE__->register_method(
1085         method => 'check_user_perms3',
1086         api_name        => 'open-ils.actor.user.perm.highest_org',
1087         notes           => q/
1088                 Returns the highest org unit id at which a user has a given permission
1089                 If the requestor does not match the target user, the requestor must have
1090                 'VIEW_PERMISSION' rights at the home org unit of the target user
1091                 @param authtoken The login session key
1092                 @param userid The id of the user in question
1093                 @param perm The permission to check
1094                 @return The org unit highest in the org tree within which the user has
1095                 the requested permission
1096         /);
1097
1098 sub check_user_perms3 {
1099         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1100
1101         my( $staff, $target, $org, $evt );
1102
1103         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1104                 $authtoken, $userid, 'VIEW_PERMISSION' );
1105         return $evt if $evt;
1106
1107         my $tree = $self->get_org_tree();
1108         return _find_highest_perm_org( $perm, $userid, $target->home_ou, $tree );
1109 }
1110
1111
1112 sub _find_highest_perm_org {
1113         my ( $perm, $userid, $start_org, $org_tree ) = @_;
1114         my $org = $apputils->find_org($org_tree, $start_org );
1115
1116         my $lastid = undef;
1117         while( $org ) {
1118                 last if ($apputils->check_perms( $userid, $org->id, $perm )); # perm failed
1119                 $lastid = $org->id;
1120                 $org = $apputils->find_org( $org_tree, $org->parent_ou() );
1121         }
1122
1123         return $lastid;
1124 }
1125
1126 __PACKAGE__->register_method(
1127         method => 'check_user_perms4',
1128         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1129         notes           => q/
1130                 Returns the highest org unit id at which a user has a given permission
1131                 If the requestor does not match the target user, the requestor must have
1132                 'VIEW_PERMISSION' rights at the home org unit of the target user
1133                 @param authtoken The login session key
1134                 @param userid The id of the user in question
1135                 @param perms An array of perm names to check 
1136                 @return An array of orgId's  representing the org unit 
1137                 highest in the org tree within which the user has the requested permission
1138                 The arrah of orgId's has matches the order of the perms array
1139         /);
1140
1141 sub check_user_perms4 {
1142         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1143         
1144         my( $staff, $target, $org, $evt );
1145
1146         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1147                 $authtoken, $userid, 'VIEW_PERMISSION' );
1148         return $evt if $evt;
1149
1150         my @arr;
1151         return [] unless ref($perms);
1152         my $tree = $self->get_org_tree();
1153
1154         for my $p (@$perms) {
1155                 push( @arr, _find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1156         }
1157         return \@arr;
1158 }
1159
1160
1161
1162
1163 __PACKAGE__->register_method(
1164         method  => "user_fines_summary",
1165         api_name        => "open-ils.actor.user.fines.summary",
1166         notes           => <<"  NOTES");
1167         Returns a short summary of the users total open fines, excluding voided fines
1168         Params are login_session, user_id
1169         Returns a 'mous' object.
1170         NOTES
1171
1172 sub user_fines_summary {
1173         my( $self, $client, $login_session, $user_id ) = @_;
1174
1175         my $user_obj = $apputils->check_user_session($login_session); 
1176         if($user_obj->id ne $user_id) {
1177                 if($apputils->check_user_perms($user_obj->id, $user_obj->home_ou, "VIEW_USER_FINES_SUMMARY")) {
1178                         return OpenILS::Perm->new("VIEW_USER_FINES_SUMMARY"); 
1179                 }
1180         }
1181
1182         return $apputils->simple_scalar_request( 
1183                 "open-ils.storage",
1184                 "open-ils.storage.direct.money.open_user_summary.search.usr",
1185                 $user_id );
1186
1187 }
1188
1189
1190
1191
1192 __PACKAGE__->register_method(
1193         method  => "user_transactions",
1194         api_name        => "open-ils.actor.user.transactions",
1195         notes           => <<"  NOTES");
1196         Returns a list of open user transactions (mbts objects);
1197         Params are login_session, user_id
1198         Optional third parameter is the transactions type.  defaults to all
1199         NOTES
1200
1201 __PACKAGE__->register_method(
1202         method  => "user_transactions",
1203         api_name        => "open-ils.actor.user.transactions.have_charge",
1204         notes           => <<"  NOTES");
1205         Returns a list of all open user transactions (mbts objects) that have an initial charge
1206         Params are login_session, user_id
1207         Optional third parameter is the transactions type.  defaults to all
1208         NOTES
1209
1210 __PACKAGE__->register_method(
1211         method  => "user_transactions",
1212         api_name        => "open-ils.actor.user.transactions.have_balance",
1213         notes           => <<"  NOTES");
1214         Returns a list of all open user transactions (mbts objects) that have a balance
1215         Params are login_session, user_id
1216         Optional third parameter is the transactions type.  defaults to all
1217         NOTES
1218
1219 __PACKAGE__->register_method(
1220         method  => "user_transactions",
1221         api_name        => "open-ils.actor.user.transactions.fleshed",
1222         notes           => <<"  NOTES");
1223         Returns an object/hash of transaction, circ, title where transaction = an open 
1224         user transactions (mbts objects), circ is the attached circluation, and title
1225         is the title the circ points to
1226         Params are login_session, user_id
1227         Optional third parameter is the transactions type.  defaults to all
1228         NOTES
1229
1230 __PACKAGE__->register_method(
1231         method  => "user_transactions",
1232         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1233         notes           => <<"  NOTES");
1234         Returns an object/hash of transaction, circ, title where transaction = an open 
1235         user transactions that has an initial charge (mbts objects), circ is the 
1236         attached circluation, and title is the title the circ points to
1237         Params are login_session, user_id
1238         Optional third parameter is the transactions type.  defaults to all
1239         NOTES
1240
1241 __PACKAGE__->register_method(
1242         method  => "user_transactions",
1243         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1244         notes           => <<"  NOTES");
1245         Returns an object/hash of transaction, circ, title where transaction = an open 
1246         user transaction that has a balance (mbts objects), circ is the attached 
1247         circluation, and title is the title the circ points to
1248         Params are login_session, user_id
1249         Optional third parameter is the transaction type.  defaults to all
1250         NOTES
1251
1252 __PACKAGE__->register_method(
1253         method  => "user_transactions",
1254         api_name        => "open-ils.actor.user.transactions.count",
1255         notes           => <<"  NOTES");
1256         Returns an object/hash of transaction, circ, title where transaction = an open 
1257         user transactions (mbts objects), circ is the attached circluation, and title
1258         is the title the circ points to
1259         Params are login_session, user_id
1260         Optional third parameter is the transactions type.  defaults to all
1261         NOTES
1262
1263 __PACKAGE__->register_method(
1264         method  => "user_transactions",
1265         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1266         notes           => <<"  NOTES");
1267         Returns an object/hash of transaction, circ, title where transaction = an open 
1268         user transactions that has an initial charge (mbts objects), circ is the 
1269         attached circluation, and title is the title the circ points to
1270         Params are login_session, user_id
1271         Optional third parameter is the transactions type.  defaults to all
1272         NOTES
1273
1274 __PACKAGE__->register_method(
1275         method  => "user_transactions",
1276         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1277         notes           => <<"  NOTES");
1278         Returns an object/hash of transaction, circ, title where transaction = an open 
1279         user transaction that has a balance (mbts objects), circ is the attached 
1280         circluation, and title is the title the circ points to
1281         Params are login_session, user_id
1282         Optional third parameter is the transaction type.  defaults to all
1283         NOTES
1284
1285 __PACKAGE__->register_method(
1286         method  => "user_transactions",
1287         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1288         notes           => <<"  NOTES");
1289         Returns an object/hash of transaction, circ, title where transaction = an open 
1290         user transaction that has a balance (mbts objects), circ is the attached 
1291         circluation, and title is the title the circ points to
1292         Params are login_session, user_id
1293         Optional third parameter is the transaction type.  defaults to all
1294         NOTES
1295
1296
1297
1298 sub user_transactions {
1299         my( $self, $client, $login_session, $user_id, $type ) = @_;
1300
1301         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1302                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1303         return $evt if $evt;
1304         
1305         my $api = $self->api_name();
1306         my $trans;
1307         my @xact;
1308
1309         if(defined($type)) { @xact = (xact_type =>  $type); 
1310
1311         } else { @xact = (); }
1312
1313         if($api =~ /have_charge/o) {
1314
1315                 $trans = $apputils->simple_scalar_request( 
1316                         "open-ils.storage",
1317                         "open-ils.storage.direct.money.open_billable_transaction_summary.search_where.atomic",
1318                         { usr => $user_id, total_owed => { ">" => 0 }, @xact });
1319
1320         } elsif($api =~ /have_balance/o) {
1321
1322                 $trans =  $apputils->simple_scalar_request( 
1323                         "open-ils.storage",
1324                         "open-ils.storage.direct.money.open_billable_transaction_summary.search_where.atomic",
1325                         { usr => $user_id, balance_owed => { ">" => 0 }, @xact });
1326
1327         } else {
1328
1329                 $trans =  $apputils->simple_scalar_request( 
1330                         "open-ils.storage",
1331                         "open-ils.storage.direct.money.open_billable_transaction_summary.search_where.atomic",
1332                         { usr => $user_id, @xact });
1333         }
1334
1335         if($api =~ /total/o) { 
1336                 my $total = 0.0;
1337                 for my $t (@$trans) {
1338                         $total += $t->balance_owed;
1339                 }
1340
1341                 $logger->debug("Total balance owed by user $user_id: $total");
1342                 return $total;
1343         }
1344
1345         if($api =~ /count/o) { return scalar @$trans; }
1346         if($api !~ /fleshed/o) { return $trans; }
1347
1348         #warn "API: $api\n";
1349
1350         my @resp;
1351         for my $t (@$trans) {
1352                         
1353                 #warn $t->id . "\n";
1354
1355
1356                 if( $t->xact_type ne 'circulation' ) {
1357                         push @resp, {transaction => $t};
1358                         next;
1359                 }
1360
1361                 my $circ = $apputils->simple_scalar_request(
1362                                 "open-ils.storage",
1363                                 "open-ils.storage.direct.action.circulation.retrieve",
1364                                 $t->id );
1365
1366                 next unless $circ;
1367
1368                 my $title = $apputils->simple_scalar_request(
1369                         "open-ils.storage", 
1370                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1371                         $circ->target_copy );
1372
1373                 next unless $title;
1374
1375                 my $u = OpenILS::Utils::ModsParser->new();
1376                 $u->start_mods_batch($title->marc());
1377                 my $mods = $u->finish_mods_batch();
1378
1379                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1380
1381         }
1382
1383         return \@resp; 
1384
1385
1386
1387 __PACKAGE__->register_method(
1388         method  => "user_transaction_retrieve",
1389         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1390         argc            => 1,
1391         notes           => <<"  NOTES");
1392         Returns a fleshedtransaction record
1393         NOTES
1394 __PACKAGE__->register_method(
1395         method  => "user_transaction_retrieve",
1396         api_name        => "open-ils.actor.user.transaction.retrieve",
1397         argc            => 1,
1398         notes           => <<"  NOTES");
1399         Returns a transaction record
1400         NOTES
1401 sub user_transaction_retrieve {
1402         my( $self, $client, $login_session, $bill_id ) = @_;
1403
1404         my $trans = $apputils->simple_scalar_request( 
1405                 "open-ils.storage",
1406                 "open-ils.storage.direct.money.billable_transaction_summary.retrieve",
1407                 $bill_id
1408         );
1409
1410         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1411                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1412         return $evt if $evt;
1413         
1414         my $api = $self->api_name();
1415         if($api !~ /fleshed/o) { return $trans; }
1416
1417         if( $trans->xact_type ne 'circulation' ) {
1418                 $logger->debug("Returning non-circ transaction");
1419                 return {transaction => $trans};
1420         }
1421
1422         my $circ = $apputils->simple_scalar_request(
1423                         "open-ils.storage",
1424                         "open-ils.storage.direct.action.circulation.retrieve",
1425                         $trans->id );
1426
1427         return {transaction => $trans} unless $circ;
1428         $logger->debug("Found the circ transaction");
1429
1430         my $title = $apputils->simple_scalar_request(
1431                 "open-ils.storage", 
1432                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1433                 $circ->target_copy );
1434
1435         return {transaction => $trans, circ => $circ } unless $title;
1436         $logger->debug("Found the circ title");
1437
1438         my $mods;
1439         try {
1440                 my $u = OpenILS::Utils::ModsParser->new();
1441                 $u->start_mods_batch($title->marc());
1442                 $mods = $u->finish_mods_batch();
1443         } otherwise {
1444                 if ($title->id == -1) {
1445                         my $copy = $apputils->simple_scalar_request(
1446                                 "open-ils.storage",
1447                                 "open-ils.storage.direct.asset.copy.retrieve",
1448                                 $circ->target_copy );
1449
1450                         $mods = new Fieldmapper::metabib::virtual_record;
1451                         $mods->doc_id(-1);
1452                         $mods->title($copy->dummy_title);
1453                         $mods->author($copy->dummy_author);
1454                 }
1455         };
1456
1457         $logger->debug("MODSized the circ title");
1458
1459         return {transaction => $trans, circ => $circ, record => $mods };
1460 }
1461
1462
1463 __PACKAGE__->register_method(
1464         method  => "hold_request_count",
1465         api_name        => "open-ils.actor.user.hold_requests.count",
1466         argc            => 1,
1467         notes           => <<"  NOTES");
1468         Returns hold ready/total counts
1469         NOTES
1470 sub hold_request_count {
1471         my( $self, $client, $login_session, $userid ) = @_;
1472
1473         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1474                 $login_session, $userid, 'VIEW_HOLD' );
1475         return $evt if $evt;
1476         
1477
1478         my $holds = $apputils->simple_scalar_request(
1479                         "open-ils.storage",
1480                         "open-ils.storage.direct.action.hold_request.search_where.atomic",
1481                         { usr => $userid,
1482                           fulfillment_time => {"=" => undef } }
1483         );
1484
1485         my @ready;
1486         for my $h (@$holds) {
1487                 next unless $h->capture_time;
1488
1489                 my $copy = $apputils->simple_scalar_request(
1490                         "open-ils.storage",
1491                         "open-ils.storage.direct.asset.copy.retrieve",
1492                         $h->current_copy
1493                 );
1494
1495                 if ($copy->status == 8) {
1496                         push @ready, $h;
1497                 }
1498         }
1499
1500         return { total => scalar(@$holds), ready => scalar(@ready) };
1501 }
1502
1503
1504 __PACKAGE__->register_method(
1505         method  => "checkedout_count",
1506         api_name        => "open-ils.actor.user.checked_out.count",
1507         argc            => 1,
1508         notes           => <<"  NOTES");
1509         Returns a transaction record
1510         NOTES
1511 sub checkedout_count {
1512         my( $self, $client, $login_session, $userid ) = @_;
1513
1514         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1515                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1516         return $evt if $evt;
1517         
1518
1519         my $circs = $apputils->simple_scalar_request(
1520                         "open-ils.storage",
1521                         "open-ils.storage.direct.action.circulation.search_where.atomic",
1522                         { usr => $userid,
1523                           checkin_time => {"=" => undef } }
1524         );
1525
1526         my $parser = DateTime::Format::ISO8601->new;
1527
1528         my (@out,@overdue);
1529         for my $c (@$circs) {
1530                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1531                 my $due = $due_dt->epoch;
1532
1533                 if ($due < time) {
1534                         push @overdue, $c;
1535                 }
1536         }
1537
1538         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1539 }
1540
1541 __PACKAGE__->register_method(
1542         method  => "user_transaction_history",
1543         api_name        => "open-ils.actor.user.transactions.history",
1544         argc            => 1,
1545         notes           => <<"  NOTES");
1546         Returns a list of billable transaction ids for a user, optionally by type
1547         NOTES
1548 sub user_transaction_history {
1549         my( $self, $client, $login_session, $user_id, $type ) = @_;
1550
1551         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1552                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1553         return $evt if $evt;
1554         
1555         my $api = $self->api_name();
1556         my @xact;
1557
1558         @xact = (xact_type =>  $type) if(defined($type));
1559
1560         my $trans = $apputils->simple_scalar_request( 
1561                 "open-ils.storage",
1562                 "open-ils.storage.direct.money.billable_transaction_summary.search_where.atomic",
1563                 { usr => $user_id, @xact }, { order_by => 'xact_start DESC' });
1564
1565         return [ map { $_->id } @$trans ];
1566 }
1567
1568
1569 __PACKAGE__->register_method(
1570         method  => "user_perms",
1571         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
1572         argc            => 1,
1573         notes           => <<"  NOTES");
1574         Returns a list of permissions
1575         NOTES
1576 sub user_perms {
1577         my( $self, $client, $authtoken, $user ) = @_;
1578
1579         my( $staff, $evt ) = $apputils->checkses($authtoken);
1580         return $evt if $evt;
1581
1582         $user ||= $staff->id;
1583
1584         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
1585                 return $evt;
1586         }
1587
1588         return $apputils->simple_scalar_request(
1589                 "open-ils.storage",
1590                 "open-ils.storage.permission.user_perms.atomic",
1591                 $user);
1592 }
1593
1594 __PACKAGE__->register_method(
1595         method  => "retrieve_perms",
1596         api_name        => "open-ils.actor.permissions.retrieve",
1597         notes           => <<"  NOTES");
1598         Returns a list of permissions
1599         NOTES
1600 sub retrieve_perms {
1601         my( $self, $client ) = @_;
1602         return $apputils->simple_scalar_request(
1603                 "open-ils.storage",
1604                 "open-ils.storage.direct.permission.perm_list.retrieve.all.atomic");
1605 }
1606
1607 __PACKAGE__->register_method(
1608         method  => "retrieve_groups",
1609         api_name        => "open-ils.actor.groups.retrieve",
1610         notes           => <<"  NOTES");
1611         Returns a list of user groupss
1612         NOTES
1613 sub retrieve_groups {
1614         my( $self, $client ) = @_;
1615         return $apputils->simple_scalar_request(
1616                 "open-ils.storage",
1617                 "open-ils.storage.direct.permission.grp_tree.retrieve.all.atomic");
1618 }
1619
1620 __PACKAGE__->register_method(
1621         method  => "retrieve_groups_tree",
1622         api_name        => "open-ils.actor.groups.tree.retrieve",
1623         notes           => <<"  NOTES");
1624         Returns a list of user groups
1625         NOTES
1626 sub retrieve_groups_tree {
1627         my( $self, $client ) = @_;
1628         my $groups = $apputils->simple_scalar_request(
1629                 "open-ils.storage",
1630                 "open-ils.storage.direct.permission.grp_tree.retrieve.all.atomic");
1631         return $self->build_group_tree($groups);        
1632 }
1633
1634
1635 # turns an org list into an org tree
1636 sub build_group_tree {
1637
1638         my( $self, $grplist) = @_;
1639
1640         return $grplist unless ( 
1641                         ref($grplist) and @$grplist > 1 );
1642
1643         my @list = sort { $a->name cmp $b->name } @$grplist;
1644
1645         my $root;
1646         for my $grp (@list) {
1647
1648                 if ($grp and !defined($grp->parent)) {
1649                         $root = $grp;
1650                         next;
1651                 }
1652                 my ($parent) = grep { $_->id == $grp->parent} @list;
1653
1654                 $parent->children([]) unless defined($parent->children); 
1655                 push( @{$parent->children}, $grp );
1656         }
1657
1658         return $root;
1659
1660 }
1661
1662
1663 __PACKAGE__->register_method(
1664         method  => "add_user_to_groups",
1665         api_name        => "open-ils.actor.user.set_groups",
1666         notes           => <<"  NOTES");
1667         Adds a user to one or more permission groups
1668         NOTES
1669
1670 sub add_user_to_groups {
1671         my( $self, $client, $authtoken, $userid, $groups ) = @_;
1672
1673         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
1674                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
1675         return $evt if $evt;
1676
1677         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
1678                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
1679         return $evt if $evt;
1680
1681         $apputils->simplereq(
1682                 'open-ils.storage',
1683                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
1684                 
1685         for my $group (@$groups) {
1686                 my $link = Fieldmapper::permission::usr_grp_map->new;
1687                 $link->grp($group);
1688                 $link->usr($userid);
1689
1690                 my $id = $apputils->simplereq(
1691                         'open-ils.storage',
1692                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
1693         }
1694
1695         return 1;
1696 }
1697
1698 __PACKAGE__->register_method(
1699         method  => "get_user_perm_groups",
1700         api_name        => "open-ils.actor.user.get_groups",
1701         notes           => <<"  NOTES");
1702         Retrieve a user's permission groups.
1703         NOTES
1704
1705
1706 sub get_user_perm_groups {
1707         my( $self, $client, $authtoken, $userid ) = @_;
1708
1709         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
1710                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
1711         return $evt if $evt;
1712
1713         return $apputils->simplereq(
1714                 'open-ils.storage',
1715                 'open-ils.storage.direct.permission.usr_grp_map.search.usr.atomic', $userid );
1716 }       
1717
1718
1719
1720
1721 1;
1722