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