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