872bd5838bc405ce23c971aa05f01eb412e40f55
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Actor.pm
1 package OpenILS::Application::Actor;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
5 use Data::Dumper;
6 $Data::Dumper::Indent = 0;
7 use OpenILS::Event;
8
9 use Digest::MD5 qw(md5_hex);
10
11 use OpenSRF::EX qw(:try);
12 use OpenILS::Perm;
13
14 use OpenILS::Application::AppUtils;
15
16 use OpenILS::Utils::Fieldmapper;
17 use OpenILS::Utils::ModsParser;
18 use OpenSRF::Utils::Logger qw/$logger/;
19 use OpenSRF::Utils qw/:datetime/;
20 use OpenSRF::Utils::SettingsClient;
21
22 use OpenSRF::Utils::Cache;
23
24 use OpenSRF::Utils::JSON;
25 use DateTime;
26 use DateTime::Format::ISO8601;
27 use OpenILS::Const qw/:const/;
28
29 use OpenILS::Application::Actor::Container;
30 use OpenILS::Application::Actor::ClosedDates;
31
32 use OpenILS::Utils::CStoreEditor qw/:funcs/;
33
34 use OpenILS::Application::Actor::UserGroups;
35 sub initialize {
36         OpenILS::Application::Actor::Container->initialize();
37         OpenILS::Application::Actor::UserGroups->initialize();
38         OpenILS::Application::Actor::ClosedDates->initialize();
39 }
40
41 my $apputils = "OpenILS::Application::AppUtils";
42 my $U = $apputils;
43
44 sub _d { warn "Patron:\n" . Dumper(shift()); }
45
46 my $cache;
47
48
49 my $set_user_settings;
50 my $set_ou_settings;
51
52 __PACKAGE__->register_method(
53         method  => "set_user_settings",
54         api_name        => "open-ils.actor.patron.settings.update",
55 );
56 sub set_user_settings {
57         my( $self, $client, $user_session, $uid, $settings ) = @_;
58         
59         $logger->debug("Setting user settings: $user_session, $uid, " . Dumper($settings));
60
61         my( $staff, $user, $evt ) = 
62                 $apputils->checkses_requestor( $user_session, $uid, 'UPDATE_USER' );    
63         return $evt if $evt;
64         
65         my @params = map { 
66                 [{ usr => $user->id, name => $_}, {value => $$settings{$_}}] } keys %$settings;
67                 
68         $_->[1]->{value} = OpenSRF::Utils::JSON->perl2JSON($_->[1]->{value}) for @params;
69
70         $logger->activity("User " . $staff->id . " updating user $uid settings with: " . Dumper(\@params));
71
72         my $ses = $U->start_db_session();
73         my $stat = $ses->request(
74                 'open-ils.storage.direct.actor.user_setting.batch.merge', @params )->gather(1);
75         $U->commit_db_session($ses);
76
77         return $stat;
78 }
79
80
81
82 __PACKAGE__->register_method(
83         method  => "set_ou_settings",
84         api_name        => "open-ils.actor.org_unit.settings.update",
85 );
86 sub set_ou_settings {
87         my( $self, $client, $user_session, $ouid, $settings ) = @_;
88         
89         my( $staff, $evt ) = $apputils->checkses( $user_session );
90         return $evt if $evt;
91         $evt = $apputils->check_perms( $staff->id, $ouid, 'UPDATE_ORG_SETTING' );
92         return $evt if $evt;
93
94         my @params;
95         for my $set (keys %$settings) {
96
97                 my $json = OpenSRF::Utils::JSON->perl2JSON($$settings{$set});
98                 $logger->activity("updating org_unit.setting: $ouid : $set : $json");
99
100                 push( @params, 
101                         { org_unit => $ouid, name => $set }, 
102                         { value => $json } );
103         }
104
105         my $ses = $U->start_db_session();
106         my $stat = $ses->request(
107                 'open-ils.storage.direct.actor.org_unit_setting.merge', @params )->gather(1);
108         $U->commit_db_session($ses);
109
110         return $stat;
111 }
112
113
114 my $fetch_user_settings;
115 my $fetch_ou_settings;
116
117 __PACKAGE__->register_method(
118         method  => "user_settings",
119         api_name        => "open-ils.actor.patron.settings.retrieve",
120 );
121 sub user_settings {
122         my( $self, $client, $user_session, $uid, $setting ) = @_;
123         
124         my( $staff, $user, $evt ) = 
125                 $apputils->checkses_requestor( $user_session, $uid, 'VIEW_USER' );
126         return $evt if $evt;
127
128         $logger->debug("User " . $staff->id . " fetching user $uid\n");
129         my $s = $apputils->simplereq(
130                 'open-ils.cstore',
131                 'open-ils.cstore.direct.actor.user_setting.search.atomic', { usr => $uid } );
132
133         my $settings =  { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
134
135    return $$settings{$setting} if $setting;
136    return $settings;
137 }
138
139
140
141 __PACKAGE__->register_method(
142         method  => "ou_settings",
143         api_name        => "open-ils.actor.org_unit.settings.retrieve",
144 );
145 sub ou_settings {
146         my( $self, $client, $ouid ) = @_;
147         
148         $logger->info("Fetching org unit settings for org $ouid");
149
150         my $s = $apputils->simplereq(
151                 'open-ils.cstore',
152                 'open-ils.cstore.direct.actor.org_unit_setting.search.atomic', {org_unit => $ouid});
153
154         return { map { ( $_->name => OpenSRF::Utils::JSON->JSON2perl($_->value) ) } @$s };
155 }
156
157
158
159 __PACKAGE__->register_method(
160     api_name => 'open-ils.actor.ou_setting.ancestor_default',
161     method => 'ou_ancestor_setting',
162 );
163
164 # ------------------------------------------------------------------
165 # Attempts to find the org setting value for a given org.  if not 
166 # found at the requested org, searches up the org tree until it 
167 # finds a parent that has the requested setting.
168 # when found, returns { org => $id, value => $value }
169 # otherwise, returns NULL
170 # ------------------------------------------------------------------
171 sub ou_ancestor_setting {
172     my( $self, $client, $orgid, $name ) = @_;
173     return $U->ou_ancestor_setting($orgid, $name);
174 }
175
176
177
178
179 __PACKAGE__->register_method (
180         method          => "ou_setting_delete",
181         api_name                => 'open-ils.actor.org_setting.delete',
182         signature       => q/
183                 Deletes a specific org unit setting for a specific location
184                 @param authtoken The login session key
185                 @param orgid The org unit whose setting we're changing
186                 @param setting The name of the setting to delete
187                 @return True value on success.
188         /
189 );
190
191 sub ou_setting_delete {
192         my( $self, $conn, $authtoken, $orgid, $setting ) = @_;
193         my( $reqr, $evt) = $U->checkses($authtoken);
194         return $evt if $evt;
195         $evt = $U->check_perms($reqr->id, $orgid, 'UPDATE_ORG_SETTING');
196         return $evt if $evt;
197
198         my $id = $U->cstorereq(
199                 'open-ils.cstore.direct.actor.org_unit_setting.id_list', 
200                 { name => $setting, org_unit => $orgid } );
201
202         $logger->debug("Retrieved setting $id in org unit setting delete");
203
204         my $s = $U->cstorereq(
205                 'open-ils.cstore.direct.actor.org_unit_setting.delete', $id );
206
207         $logger->activity("User ".$reqr->id." deleted org unit setting $id") if $s;
208         return $s;
209 }
210
211
212
213
214
215
216
217
218
219
220
221 __PACKAGE__->register_method(
222         method  => "update_patron",
223         api_name        => "open-ils.actor.patron.update",);
224
225 sub update_patron {
226         my( $self, $client, $user_session, $patron ) = @_;
227
228         my $session = $apputils->start_db_session();
229         my $err = undef;
230
231
232         $logger->info("Creating new patron...") if $patron->isnew; 
233         $logger->info("Updating Patron: " . $patron->id) unless $patron->isnew;
234
235         my( $user_obj, $evt ) = $U->checkses($user_session);
236         return $evt if $evt;
237
238         $evt = check_group_perm($session, $user_obj, $patron);
239         return $evt if $evt;
240
241
242         # $new_patron is the patron in progress.  $patron is the original patron
243         # passed in with the method.  new_patron will change as the components
244         # of patron are added/updated.
245
246         my $new_patron;
247
248         # unflesh the real items on the patron
249         $patron->card( $patron->card->id ) if(ref($patron->card));
250         $patron->billing_address( $patron->billing_address->id ) 
251                 if(ref($patron->billing_address));
252         $patron->mailing_address( $patron->mailing_address->id ) 
253                 if(ref($patron->mailing_address));
254
255         # create/update the patron first so we can use his id
256         if($patron->isnew()) {
257                 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
258                 return $evt if $evt;
259         } else { $new_patron = $patron; }
260
261         ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
262         return $evt if $evt;
263
264         ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
265         return $evt if $evt;
266
267         ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
268         return $evt if $evt;
269
270         # re-update the patron if anything has happened to him during this process
271         if($new_patron->ischanged()) {
272                 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
273                 return $evt if $evt;
274         }
275
276         ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
277         return $evt if $evt;
278
279         ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
280         return $evt if $evt;
281
282         ($new_patron, $evt) = _create_standing_penalties($session, $user_session, $patron, $new_patron, $user_obj);
283         return $evt if $evt;
284
285         $logger->activity("user ".$user_obj->id." updating/creating  user ".$new_patron->id);
286
287         my $opatron;
288         if(!$patron->isnew) {
289                 $opatron = new_editor()->retrieve_actor_user($new_patron->id);
290         }
291
292         $apputils->commit_db_session($session);
293         my $fuser =  flesh_user($new_patron->id());
294
295         if( $opatron ) {
296                 # Log the new and old patron for investigation
297                 $logger->info("$user_session updating patron object. orig patron object = ".
298                         OpenSRF::Utils::JSON->perl2JSON($opatron). " |||| new patron = ".OpenSRF::Utils::JSON->perl2JSON($fuser));
299         }
300
301
302         return $fuser;
303 }
304
305
306 sub flesh_user {
307         my $id = shift;
308         return new_flesh_user($id, [
309                 "cards",
310                 "card",
311                 "standing_penalties",
312                 "addresses",
313                 "billing_address",
314                 "mailing_address",
315                 "stat_cat_entries" ] );
316 }
317
318
319
320
321
322
323 # clone and clear stuff that would break the database
324 sub _clone_patron {
325         my $patron = shift;
326
327         my $new_patron = $patron->clone;
328         # clear these
329         $new_patron->clear_billing_address();
330         $new_patron->clear_mailing_address();
331         $new_patron->clear_addresses();
332         $new_patron->clear_card();
333         $new_patron->clear_cards();
334         $new_patron->clear_id();
335         $new_patron->clear_isnew();
336         $new_patron->clear_ischanged();
337         $new_patron->clear_isdeleted();
338         $new_patron->clear_stat_cat_entries();
339         $new_patron->clear_permissions();
340         $new_patron->clear_standing_penalties();
341
342         return $new_patron;
343 }
344
345
346 sub _add_patron {
347
348         my $session             = shift;
349         my $patron              = shift;
350         my $user_obj    = shift;
351
352         my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
353         return (undef, $evt) if $evt;
354
355         my $ex = $session->request(
356                 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
357         if( $ex and @$ex ) {
358                 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
359         }
360
361         $logger->info("Creating new user in the DB with username: ".$patron->usrname());
362
363         my $id = $session->request(
364                 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
365         return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
366
367         $logger->info("Successfully created new user [$id] in DB");
368
369         return ( $session->request( 
370                 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
371 }
372
373
374 sub check_group_perm {
375         my( $session, $requestor, $patron ) = @_;
376         my $evt;
377
378         # first let's see if the requestor has 
379         # priveleges to update this user in any way
380         if( ! $patron->isnew ) {
381                 my $p = $session->request(
382                         'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
383
384                 # If we are the requestor (trying to update our own account)
385                 # and we are not trying to change our profile, we're good
386                 if( $p->id == $requestor->id and 
387                                 $p->profile == $patron->profile ) {
388                         return undef;
389                 }
390
391
392                 $evt = group_perm_failed($session, $requestor, $p);
393                 return $evt if $evt;
394         }
395
396         # They are allowed to edit this patron.. can they put the 
397         # patron into the group requested?
398         $evt = group_perm_failed($session, $requestor, $patron);
399         return $evt if $evt;
400         return undef;
401 }
402
403
404 sub group_perm_failed {
405         my( $session, $requestor, $patron ) = @_;
406
407         my $perm;
408         my $grp;
409         my $grpid = $patron->profile;
410
411         do {
412
413                 $logger->debug("user update looking for group perm for group $grpid");
414                 $grp = $session->request(
415                         'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
416                 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
417
418         } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
419
420         $logger->info("user update checking perm $perm on user ".
421                 $requestor->id." for update/create on user username=".$patron->usrname);
422
423         my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
424         return $evt if $evt;
425         return undef;
426 }
427
428
429
430 sub _update_patron {
431         my( $session, $patron, $user_obj, $noperm) = @_;
432
433         $logger->info("Updating patron ".$patron->id." in DB");
434
435         my $evt;
436
437         if(!$noperm) {
438                 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
439                 return (undef, $evt) if $evt;
440         }
441
442         # update the password by itself to avoid the password protection magic
443         if( $patron->passwd ) {
444                 my $s = $session->request(
445                         'open-ils.storage.direct.actor.user.remote_update',
446                         {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
447                 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
448                 $patron->clear_passwd;
449         }
450
451         if(!$patron->ident_type) {
452                 $patron->clear_ident_type;
453                 $patron->clear_ident_value;
454         }
455
456     $evt = verify_last_xact($session, $patron);
457     return (undef, $evt) if $evt;
458
459         my $stat = $session->request(
460                 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
461         return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
462
463         return ($patron);
464 }
465
466 sub verify_last_xact {
467     my( $session, $patron ) = @_;
468     return undef unless $patron->id and $patron->id > 0;
469     my $p = $session->request(
470         'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
471     my $xact = $p->last_xact_id;
472     return undef unless $xact;
473     $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
474     return OpenILS::Event->new('XACT_COLLISION')
475         if $xact != $patron->last_xact_id;
476     return undef;
477 }
478
479
480 sub _check_dup_ident {
481         my( $session, $patron ) = @_;
482
483         return undef unless $patron->ident_value;
484
485         my $search = {
486                 ident_type      => $patron->ident_type, 
487                 ident_value => $patron->ident_value,
488         };
489
490         $logger->debug("patron update searching for dup ident values: " . 
491                 $patron->ident_type . ':' . $patron->ident_value);
492
493         $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
494
495         my $dups = $session->request(
496                 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
497
498
499         return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
500                 if $dups and @$dups;
501
502         return undef;
503 }
504
505
506 sub _add_update_addresses {
507
508         my $session = shift;
509         my $patron = shift;
510         my $new_patron = shift;
511
512         my $evt;
513
514         my $current_id; # id of the address before creation
515
516         for my $address (@{$patron->addresses()}) {
517
518                 next unless ref $address;
519                 $current_id = $address->id();
520
521                 if( $patron->billing_address() and
522                         $patron->billing_address() == $current_id ) {
523                         $logger->info("setting billing addr to $current_id");
524                         $new_patron->billing_address($address->id());
525                         $new_patron->ischanged(1);
526                 }
527         
528                 if( $patron->mailing_address() and
529                         $patron->mailing_address() == $current_id ) {
530                         $new_patron->mailing_address($address->id());
531                         $logger->info("setting mailing addr to $current_id");
532                         $new_patron->ischanged(1);
533                 }
534
535
536                 if($address->isnew()) {
537
538                         $address->usr($new_patron->id());
539
540                         ($address, $evt) = _add_address($session,$address);
541                         return (undef, $evt) if $evt;
542
543                         # we need to get the new id
544                         if( $patron->billing_address() and 
545                                         $patron->billing_address() == $current_id ) {
546                                 $new_patron->billing_address($address->id());
547                                 $logger->info("setting billing addr to $current_id");
548                                 $new_patron->ischanged(1);
549                         }
550
551                         if( $patron->mailing_address() and
552                                         $patron->mailing_address() == $current_id ) {
553                                 $new_patron->mailing_address($address->id());
554                                 $logger->info("setting mailing addr to $current_id");
555                                 $new_patron->ischanged(1);
556                         }
557
558                 } elsif($address->ischanged() ) {
559
560                         ($address, $evt) = _update_address($session, $address);
561                         return (undef, $evt) if $evt;
562
563                 } elsif($address->isdeleted() ) {
564
565                         if( $address->id() == $new_patron->mailing_address() ) {
566                                 $new_patron->clear_mailing_address();
567                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
568                                 return (undef, $evt) if $evt;
569                         }
570
571                         if( $address->id() == $new_patron->billing_address() ) {
572                                 $new_patron->clear_billing_address();
573                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
574                                 return (undef, $evt) if $evt;
575                         }
576
577                         $evt = _delete_address($session, $address);
578                         return (undef, $evt) if $evt;
579                 } 
580         }
581
582         return ( $new_patron, undef );
583 }
584
585
586 # adds an address to the db and returns the address with new id
587 sub _add_address {
588         my($session, $address) = @_;
589         $address->clear_id();
590
591         $logger->info("Creating new address at street ".$address->street1);
592
593         # put the address into the database
594         my $id = $session->request(
595                 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
596         return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
597
598         $address->id( $id );
599         return ($address, undef);
600 }
601
602
603 sub _update_address {
604         my( $session, $address ) = @_;
605
606         $logger->info("Updating address ".$address->id." in the DB");
607
608         my $stat = $session->request(
609                 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
610
611         return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
612         return ($address, undef);
613 }
614
615
616
617 sub _add_update_cards {
618
619         my $session = shift;
620         my $patron = shift;
621         my $new_patron = shift;
622
623         my $evt;
624
625         my $virtual_id; #id of the card before creation
626         for my $card (@{$patron->cards()}) {
627
628                 $card->usr($new_patron->id());
629
630                 if(ref($card) and $card->isnew()) {
631
632                         $virtual_id = $card->id();
633                         ( $card, $evt ) = _add_card($session,$card);
634                         return (undef, $evt) if $evt;
635
636                         #if(ref($patron->card)) { $patron->card($patron->card->id); }
637                         if($patron->card() == $virtual_id) {
638                                 $new_patron->card($card->id());
639                                 $new_patron->ischanged(1);
640                         }
641
642                 } elsif( ref($card) and $card->ischanged() ) {
643                         $evt = _update_card($session, $card);
644                         return (undef, $evt) if $evt;
645                 }
646         }
647
648         return ( $new_patron, undef );
649 }
650
651
652 # adds an card to the db and returns the card with new id
653 sub _add_card {
654         my( $session, $card ) = @_;
655         $card->clear_id();
656
657         $logger->info("Adding new patron card ".$card->barcode);
658
659         my $id = $session->request(
660                 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
661         return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
662         $logger->info("Successfully created patron card $id");
663
664         $card->id($id);
665         return ( $card, undef );
666 }
667
668
669 # returns event on error.  returns undef otherwise
670 sub _update_card {
671         my( $session, $card ) = @_;
672         $logger->info("Updating patron card ".$card->id);
673
674         my $stat = $session->request(
675                 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
676         return $U->DB_UPDATE_FAILED($card) unless defined($stat);
677         return undef;
678 }
679
680
681
682
683 # returns event on error.  returns undef otherwise
684 sub _delete_address {
685         my( $session, $address ) = @_;
686
687         $logger->info("Deleting address ".$address->id." from DB");
688
689         my $stat = $session->request(
690                 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
691
692         return $U->DB_UPDATE_FAILED($address) unless defined($stat);
693         return undef;
694 }
695
696
697
698 sub _add_survey_responses {
699         my ($session, $patron, $new_patron) = @_;
700
701         $logger->info( "Updating survey responses for patron ".$new_patron->id );
702
703         my $responses = $patron->survey_responses;
704
705         if($responses) {
706
707                 $_->usr($new_patron->id) for (@$responses);
708
709                 my $evt = $U->simplereq( "open-ils.circ", 
710                         "open-ils.circ.survey.submit.user_id", $responses );
711
712                 return (undef, $evt) if defined($U->event_code($evt));
713
714         }
715
716         return ( $new_patron, undef );
717 }
718
719
720 sub _create_stat_maps {
721
722         my($session, $user_session, $patron, $new_patron) = @_;
723
724         my $maps = $patron->stat_cat_entries();
725
726         for my $map (@$maps) {
727
728                 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
729
730                 if ($map->isdeleted()) {
731                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
732
733                 } elsif ($map->isnew()) {
734                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
735                         $map->clear_id;
736                 }
737
738
739                 $map->target_usr($new_patron->id);
740
741                 #warn "
742                 $logger->info("Updating stat entry with method $method and map $map");
743
744                 my $stat = $session->request($method, $map)->gather(1);
745                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
746
747         }
748
749         return ($new_patron, undef);
750 }
751
752 sub _create_perm_maps {
753
754         my($session, $user_session, $patron, $new_patron) = @_;
755
756         my $maps = $patron->permissions;
757
758         for my $map (@$maps) {
759
760                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
761                 if ($map->isdeleted()) {
762                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
763                 } elsif ($map->isnew()) {
764                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
765                         $map->clear_id;
766                 }
767
768
769                 $map->usr($new_patron->id);
770
771                 #warn( "Updating permissions with method $method and session $user_session and map $map" );
772                 $logger->info( "Updating permissions with method $method and map $map" );
773
774                 my $stat = $session->request($method, $map)->gather(1);
775                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
776
777         }
778
779         return ($new_patron, undef);
780 }
781
782
783 __PACKAGE__->register_method(
784         method  => "set_user_work_ous",
785         api_name        => "open-ils.actor.user.work_ous.update",
786 );
787
788 sub set_user_work_ous {
789         my $self = shift;
790         my $client = shift;
791         my $ses = shift;
792         my $maps = shift;
793
794         my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
795         return $evt if $evt;
796
797         my $session = $apputils->start_db_session();
798
799         for my $map (@$maps) {
800
801                 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
802                 if ($map->isdeleted()) {
803                         $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
804                 } elsif ($map->isnew()) {
805                         $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
806                         $map->clear_id;
807                 }
808
809                 #warn( "Updating permissions with method $method and session $ses and map $map" );
810                 $logger->info( "Updating work_ou map with method $method and map $map" );
811
812                 my $stat = $session->request($method, $map)->gather(1);
813                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
814
815         }
816
817         $apputils->commit_db_session($session);
818
819         return scalar(@$maps);
820 }
821
822
823 __PACKAGE__->register_method(
824         method  => "set_user_perms",
825         api_name        => "open-ils.actor.user.permissions.update",
826 );
827
828 sub set_user_perms {
829         my $self = shift;
830         my $client = shift;
831         my $ses = shift;
832         my $maps = shift;
833
834         my $session = $apputils->start_db_session();
835
836         my( $user_obj, $evt ) = $U->checkses($ses);
837         return $evt if $evt;
838
839         my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
840
841         my $all = undef;
842         $all = 1 if ($user_obj->super_user());
843     $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
844
845         for my $map (@$maps) {
846
847                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
848                 if ($map->isdeleted()) {
849                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
850                 } elsif ($map->isnew()) {
851                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
852                         $map->clear_id;
853                 }
854
855                 next if (!$all || !grep { $_->perm eq $map->perm and $_->grantable == 1 and $_->depth <= $map->depth } @$perms);
856
857                 #warn( "Updating permissions with method $method and session $ses and map $map" );
858                 $logger->info( "Updating permissions with method $method and map $map" );
859
860                 my $stat = $session->request($method, $map)->gather(1);
861                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
862
863         }
864
865         $apputils->commit_db_session($session);
866
867         return scalar(@$maps);
868 }
869
870
871 sub _create_standing_penalties {
872
873         my($session, $user_session, $patron, $new_patron) = @_;
874
875         my $maps = $patron->standing_penalties;
876         my $method;
877
878         for my $map (@$maps) {
879
880                 if ($map->isdeleted()) {
881                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
882                 } elsif ($map->isnew()) {
883                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
884                         $map->clear_id;
885                 } else {
886                         next;
887                 }
888
889                 $map->usr($new_patron->id);
890
891                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
892
893                 my $stat = $session->request($method, $map)->gather(1);
894                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
895         }
896
897         return ($new_patron, undef);
898 }
899
900
901
902 __PACKAGE__->register_method(
903         method  => "search_username",
904         api_name        => "open-ils.actor.user.search.username",
905 );
906
907 sub search_username {
908         my($self, $client, $username) = @_;
909     return new_editor()->search_actor_user({usrname=>$username});
910 }
911
912
913
914
915 __PACKAGE__->register_method(
916         method  => "user_retrieve_by_barcode",
917         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
918
919 sub user_retrieve_by_barcode {
920         my($self, $client, $user_session, $barcode) = @_;
921
922         $logger->debug("Searching for user with barcode $barcode");
923         my ($user_obj, $evt) = $apputils->checkses($user_session);
924         return $evt if $evt;
925
926         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
927                         "open-ils.cstore", 
928                         "open-ils.cstore.direct.actor.card.search.atomic",
929                         { barcode => $barcode }
930         );
931
932         if(!$card || !$card->[0]) {
933                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
934         }
935
936         $card = $card->[0];
937         my $user = flesh_user($card->usr());
938
939         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
940         return $evt if $evt;
941
942         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
943         return $user;
944
945 }
946
947
948
949 __PACKAGE__->register_method(
950         method  => "get_user_by_id",
951         api_name        => "open-ils.actor.user.retrieve",);
952
953 sub get_user_by_id {
954         my ($self, $client, $auth, $id) = @_;
955         my $e = new_editor(authtoken=>$auth);
956         return $e->event unless $e->checkauth;
957         my $user = $e->retrieve_actor_user($id)
958                 or return $e->event;
959         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
960         return $user;
961 }
962
963
964
965 __PACKAGE__->register_method(
966         method  => "get_org_types",
967         api_name        => "open-ils.actor.org_types.retrieve",);
968
969 sub get_org_types {
970     return $U->get_org_types();
971 }
972
973
974
975 __PACKAGE__->register_method(
976         method  => "get_user_ident_types",
977         api_name        => "open-ils.actor.user.ident_types.retrieve",
978 );
979 my $ident_types;
980 sub get_user_ident_types {
981         return $ident_types if $ident_types;
982         return $ident_types = 
983                 new_editor()->retrieve_all_config_identification_type();
984 }
985
986
987
988
989 __PACKAGE__->register_method(
990         method  => "get_org_unit",
991         api_name        => "open-ils.actor.org_unit.retrieve",
992 );
993
994 sub get_org_unit {
995         my( $self, $client, $user_session, $org_id ) = @_;
996         my $e = new_editor(authtoken => $user_session);
997         if(!$org_id) {
998                 return $e->event unless $e->checkauth;
999                 $org_id = $e->requestor->ws_ou;
1000         }
1001         my $o = $e->retrieve_actor_org_unit($org_id)
1002                 or return $e->event;
1003         return $o;
1004 }
1005
1006 __PACKAGE__->register_method(
1007         method  => "search_org_unit",
1008         api_name        => "open-ils.actor.org_unit_list.search",
1009 );
1010
1011 sub search_org_unit {
1012
1013         my( $self, $client, $field, $value ) = @_;
1014
1015         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1016                 "open-ils.cstore",
1017                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
1018                 { $field => $value } );
1019
1020         return $list;
1021 }
1022
1023
1024 # build the org tree
1025
1026 __PACKAGE__->register_method(
1027         method  => "get_org_tree",
1028         api_name        => "open-ils.actor.org_tree.retrieve",
1029         argc            => 0, 
1030         note            => "Returns the entire org tree structure",
1031 );
1032
1033 sub get_org_tree {
1034         my( $self, $client) = @_;
1035     return $U->get_org_tree();
1036 }
1037
1038
1039 __PACKAGE__->register_method(
1040         method  => "get_org_descendants",
1041         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1042 );
1043
1044 # depth is optional.  org_unit is the id
1045 sub get_org_descendants {
1046         my( $self, $client, $org_unit, $depth ) = @_;
1047         my $orglist = $apputils->simple_scalar_request(
1048                         "open-ils.storage", 
1049                         "open-ils.storage.actor.org_unit.descendants.atomic",
1050                         $org_unit, $depth );
1051         return $U->build_org_tree($orglist);
1052 }
1053
1054
1055 __PACKAGE__->register_method(
1056         method  => "get_org_ancestors",
1057         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1058 );
1059
1060 # depth is optional.  org_unit is the id
1061 sub get_org_ancestors {
1062         my( $self, $client, $org_unit, $depth ) = @_;
1063         my $orglist = $apputils->simple_scalar_request(
1064                         "open-ils.storage", 
1065                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1066                         $org_unit, $depth );
1067         return $U->build_org_tree($orglist);
1068 }
1069
1070
1071 __PACKAGE__->register_method(
1072         method  => "get_standings",
1073         api_name        => "open-ils.actor.standings.retrieve"
1074 );
1075
1076 my $user_standings;
1077 sub get_standings {
1078         return $user_standings if $user_standings;
1079         return $user_standings = 
1080                 $apputils->simple_scalar_request(
1081                         "open-ils.cstore",
1082                         "open-ils.cstore.direct.config.standing.search.atomic",
1083                         { id => { "!=" => undef } }
1084                 );
1085 }
1086
1087
1088
1089 __PACKAGE__->register_method(
1090         method  => "get_my_org_path",
1091         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1092 );
1093
1094 sub get_my_org_path {
1095         my( $self, $client, $auth, $org_id ) = @_;
1096         my $e = new_editor(authtoken=>$auth);
1097         return $e->event unless $e->checkauth;
1098         $org_id = $e->requestor->ws_ou unless defined $org_id;
1099
1100         return $apputils->simple_scalar_request(
1101                 "open-ils.storage",
1102                 "open-ils.storage.actor.org_unit.full_path.atomic",
1103                 $org_id );
1104 }
1105
1106
1107 __PACKAGE__->register_method(
1108         method  => "patron_adv_search",
1109         api_name        => "open-ils.actor.patron.search.advanced" );
1110 sub patron_adv_search {
1111         my( $self, $client, $auth, $search_hash, 
1112         $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1113
1114         my $e = new_editor(authtoken=>$auth);
1115         return $e->event unless $e->checkauth;
1116         return $e->event unless $e->allowed('VIEW_USER');
1117         return $U->storagereq(
1118                 "open-ils.storage.actor.user.crazy_search", $search_hash, 
1119             $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1120 }
1121
1122
1123 __PACKAGE__->register_method(
1124         method  => "update_passwd",
1125     authoritative => 1,
1126         api_name        => "open-ils.actor.user.password.update");
1127
1128 __PACKAGE__->register_method(
1129         method  => "update_passwd",
1130         api_name        => "open-ils.actor.user.username.update");
1131
1132 __PACKAGE__->register_method(
1133         method  => "update_passwd",
1134         api_name        => "open-ils.actor.user.email.update");
1135
1136 sub update_passwd {
1137     my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1138     my $e = new_editor(xact=>1, authtoken=>$auth);
1139     return $e->die_event unless $e->checkauth;
1140
1141     my $db_user = $e->retrieve_actor_user($e->requestor->id)
1142         or return $e->die_event;
1143     my $api = $self->api_name;
1144
1145     if( $api =~ /password/o ) {
1146
1147         # make sure the original password matches the in-database password
1148         return OpenILS::Event->new('INCORRECT_PASSWORD')
1149             if md5_hex($orig_pw) ne $db_user->passwd;
1150         $db_user->passwd($new_val);
1151
1152     } else {
1153
1154         # if we don't clear the password, the user will be updated with
1155         # a hashed version of the hashed version of their password
1156         $db_user->clear_passwd;
1157
1158         if( $api =~ /username/o ) {
1159
1160             # make sure no one else has this username
1161             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
1162                         return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1163             $db_user->usrname($new_val);
1164
1165         } elsif( $api =~ /email/o ) {
1166             $db_user->email($new_val);
1167         }
1168     }
1169
1170     $e->update_actor_user($db_user) or return $e->die_event;
1171     $e->commit;
1172     return 1;
1173 }
1174
1175
1176
1177
1178 __PACKAGE__->register_method(
1179         method  => "check_user_perms",
1180         api_name        => "open-ils.actor.user.perm.check",
1181         notes           => <<"  NOTES");
1182         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1183         perm type, if the user does *not* have the given permission it is added
1184         to a list which is returned from the method.  If all permissions
1185         are allowed, an empty list is returned
1186         if the logged in user does not match 'user_id', then the logged in user must
1187         have VIEW_PERMISSION priveleges.
1188         NOTES
1189
1190 sub check_user_perms {
1191         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1192
1193         my( $staff, $evt ) = $apputils->checkses($login_session);
1194         return $evt if $evt;
1195
1196         if($staff->id ne $user_id) {
1197                 if( $evt = $apputils->check_perms(
1198                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1199                         return $evt;
1200                 }
1201         }
1202
1203         my @not_allowed;
1204         for my $perm (@$perm_types) {
1205                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1206                         push @not_allowed, $perm;
1207                 }
1208         }
1209
1210         return \@not_allowed
1211 }
1212
1213 __PACKAGE__->register_method(
1214         method  => "check_user_perms2",
1215         api_name        => "open-ils.actor.user.perm.check.multi_org",
1216         notes           => q/
1217                 Checks the permissions on a list of perms and orgs for a user
1218                 @param authtoken The login session key
1219                 @param user_id The id of the user to check
1220                 @param orgs The array of org ids
1221                 @param perms The array of permission names
1222                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1223                 if the logged in user does not match 'user_id', then the logged in user must
1224                 have VIEW_PERMISSION priveleges.
1225         /);
1226
1227 sub check_user_perms2 {
1228         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1229
1230         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1231                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1232         return $evt if $evt;
1233
1234         my @not_allowed;
1235         for my $org (@$orgs) {
1236                 for my $perm (@$perms) {
1237                         if($apputils->check_perms($user_id, $org, $perm)) {
1238                                 push @not_allowed, [ $org, $perm ];
1239                         }
1240                 }
1241         }
1242
1243         return \@not_allowed
1244 }
1245
1246
1247 __PACKAGE__->register_method(
1248         method => 'check_user_perms3',
1249         api_name        => 'open-ils.actor.user.perm.highest_org',
1250         notes           => q/
1251                 Returns the highest org unit id at which a user has a given permission
1252                 If the requestor does not match the target user, the requestor must have
1253                 'VIEW_PERMISSION' rights at the home org unit of the target user
1254                 @param authtoken The login session key
1255                 @param userid The id of the user in question
1256                 @param perm The permission to check
1257                 @return The org unit highest in the org tree within which the user has
1258                 the requested permission
1259         /);
1260
1261 sub check_user_perms3 {
1262         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1263
1264         my( $staff, $target, $org, $evt );
1265
1266         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1267                 $authtoken, $userid, 'VIEW_PERMISSION' );
1268         return $evt if $evt;
1269
1270         my $tree = $U->get_org_tree();
1271         return $U->find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1272 }
1273
1274
1275 __PACKAGE__->register_method(
1276         method => 'check_user_work_perms',
1277         api_name        => 'open-ils.actor.user.work_perm.highest_org_set',
1278     authoritative => 1,
1279     signature => {
1280         desc => q/
1281             Returns a set of org units which represent the highest orgs in 
1282             the org tree where the user has the requested permission.  The
1283             purpose of this method is to return the smallest set of org units
1284             which represent the full expanse of the user's ability to perform
1285             the requested action.  The user whose perms this method should
1286             check is implied by the authtoken. /,
1287         params => [
1288                     {desc => 'authtoken', type => 'string'},
1289             {desc => 'permission name', type => 'string'},
1290             {desc => 'options hash, including "descendants", which will include all child orgs of the found perm orgs', type => 'hash'}
1291         ],
1292         return => {desc => 'An array of org IDs'}
1293     }
1294 );
1295
1296 sub check_user_work_perms {
1297     my($self, $conn, $auth, $perm, $options) = @_;
1298     my $e = new_editor(authtoken=>$auth);
1299     return $e->event unless $e->checkauth;
1300     return $U->find_highest_work_orgs($e, $perm, $options);
1301 }
1302
1303 __PACKAGE__->register_method(
1304         method => 'check_user_perms4',
1305         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1306         notes           => q/
1307                 Returns the highest org unit id at which a user has a given permission
1308                 If the requestor does not match the target user, the requestor must have
1309                 'VIEW_PERMISSION' rights at the home org unit of the target user
1310                 @param authtoken The login session key
1311                 @param userid The id of the user in question
1312                 @param perms An array of perm names to check 
1313                 @return An array of orgId's  representing the org unit 
1314                 highest in the org tree within which the user has the requested permission
1315                 The arrah of orgId's has matches the order of the perms array
1316         /);
1317
1318 sub check_user_perms4 {
1319         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1320         
1321         my( $staff, $target, $org, $evt );
1322
1323         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1324                 $authtoken, $userid, 'VIEW_PERMISSION' );
1325         return $evt if $evt;
1326
1327         my @arr;
1328         return [] unless ref($perms);
1329         my $tree = $U->get_org_tree();
1330
1331         for my $p (@$perms) {
1332                 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1333         }
1334         return \@arr;
1335 }
1336
1337
1338
1339
1340 __PACKAGE__->register_method(
1341         method  => "user_fines_summary",
1342         api_name        => "open-ils.actor.user.fines.summary",
1343         notes           => <<"  NOTES");
1344         Returns a short summary of the users total open fines, excluding voided fines
1345         Params are login_session, user_id
1346         Returns a 'mous' object.
1347         NOTES
1348
1349 sub user_fines_summary {
1350         my( $self, $client, $auth, $user_id ) = @_;
1351         my $e = new_editor(authtoken=>$auth);
1352         return $e->event unless $e->checkauth;
1353         my $user = $e->retrieve_actor_user($user_id)
1354                 or return $e->event;
1355
1356         if( $user_id ne $e->requestor->id ) {
1357                 return $e->event unless 
1358                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1359         }
1360         
1361         # run this inside a transaction to prevent replication delay errors
1362         my $ses = $U->start_db_session();
1363         my $s = $ses->request(
1364                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1365         $U->rollback_db_session($ses);
1366         return $s;
1367 }
1368
1369
1370
1371
1372 __PACKAGE__->register_method(
1373         method  => "user_transactions",
1374         api_name        => "open-ils.actor.user.transactions",
1375         notes           => <<"  NOTES");
1376         Returns a list of open user transactions (mbts objects);
1377         Params are login_session, user_id
1378         Optional third parameter is the transactions type.  defaults to all
1379         NOTES
1380
1381 __PACKAGE__->register_method(
1382         method  => "user_transactions",
1383         api_name        => "open-ils.actor.user.transactions.have_charge",
1384         notes           => <<"  NOTES");
1385         Returns a list of all open user transactions (mbts objects) that have an initial charge
1386         Params are login_session, user_id
1387         Optional third parameter is the transactions type.  defaults to all
1388         NOTES
1389
1390 __PACKAGE__->register_method(
1391         method  => "user_transactions",
1392         api_name        => "open-ils.actor.user.transactions.have_balance",
1393         notes           => <<"  NOTES");
1394         Returns a list of all open user transactions (mbts objects) that have a balance
1395         Params are login_session, user_id
1396         Optional third parameter is the transactions type.  defaults to all
1397         NOTES
1398
1399 __PACKAGE__->register_method(
1400         method  => "user_transactions",
1401         api_name        => "open-ils.actor.user.transactions.fleshed",
1402         notes           => <<"  NOTES");
1403         Returns an object/hash of transaction, circ, title where transaction = an open 
1404         user transactions (mbts objects), circ is the attached circluation, and title
1405         is the title the circ points to
1406         Params are login_session, user_id
1407         Optional third parameter is the transactions type.  defaults to all
1408         NOTES
1409
1410 __PACKAGE__->register_method(
1411         method  => "user_transactions",
1412         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1413         notes           => <<"  NOTES");
1414         Returns an object/hash of transaction, circ, title where transaction = an open 
1415         user transactions that has an initial charge (mbts objects), circ is the 
1416         attached circluation, and title is the title the circ points to
1417         Params are login_session, user_id
1418         Optional third parameter is the transactions type.  defaults to all
1419         NOTES
1420
1421 __PACKAGE__->register_method(
1422         method  => "user_transactions",
1423         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1424         notes           => <<"  NOTES");
1425         Returns an object/hash of transaction, circ, title where transaction = an open 
1426         user transaction that has a balance (mbts objects), circ is the attached 
1427         circluation, and title is the title the circ points to
1428         Params are login_session, user_id
1429         Optional third parameter is the transaction type.  defaults to all
1430         NOTES
1431
1432 __PACKAGE__->register_method(
1433         method  => "user_transactions",
1434         api_name        => "open-ils.actor.user.transactions.count",
1435         notes           => <<"  NOTES");
1436         Returns an object/hash of transaction, circ, title where transaction = an open 
1437         user transactions (mbts objects), circ is the attached circluation, and title
1438         is the title the circ points to
1439         Params are login_session, user_id
1440         Optional third parameter is the transactions type.  defaults to all
1441         NOTES
1442
1443 __PACKAGE__->register_method(
1444         method  => "user_transactions",
1445         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1446         notes           => <<"  NOTES");
1447         Returns an object/hash of transaction, circ, title where transaction = an open 
1448         user transactions that has an initial charge (mbts objects), circ is the 
1449         attached circluation, and title is the title the circ points to
1450         Params are login_session, user_id
1451         Optional third parameter is the transactions type.  defaults to all
1452         NOTES
1453
1454 __PACKAGE__->register_method(
1455         method  => "user_transactions",
1456         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1457         notes           => <<"  NOTES");
1458         Returns an object/hash of transaction, circ, title where transaction = an open 
1459         user transaction that has a balance (mbts objects), circ is the attached 
1460         circluation, and title is the title the circ points to
1461         Params are login_session, user_id
1462         Optional third parameter is the transaction type.  defaults to all
1463         NOTES
1464
1465 __PACKAGE__->register_method(
1466         method  => "user_transactions",
1467         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1468         notes           => <<"  NOTES");
1469         Returns an object/hash of transaction, circ, title where transaction = an open 
1470         user transaction that has a balance (mbts objects), circ is the attached 
1471         circluation, and title is the title the circ points to
1472         Params are login_session, user_id
1473         Optional third parameter is the transaction type.  defaults to all
1474         NOTES
1475
1476
1477
1478 sub user_transactions {
1479         my( $self, $client, $login_session, $user_id, $type ) = @_;
1480
1481         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1482                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1483         return $evt if $evt;
1484
1485         my $api = $self->api_name();
1486         my $trans;
1487         my @xact;
1488
1489         if(defined($type)) { @xact = (xact_type =>  $type); 
1490
1491         } else { @xact = (); }
1492
1493         ($trans) = $self
1494                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1495                 ->run($login_session => $user_id => $type);
1496
1497         if($api =~ /have_charge/o) {
1498
1499                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1500
1501         } elsif($api =~ /have_balance/o) {
1502
1503                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1504         } else {
1505
1506                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1507
1508         }
1509
1510         if($api =~ /total/o) { 
1511                 my $total = 0.0;
1512                 for my $t (@$trans) {
1513                         $total += $t->balance_owed;
1514                 }
1515
1516                 $logger->debug("Total balance owed by user $user_id: $total");
1517                 return $total;
1518         }
1519
1520         if($api =~ /count/o) { return scalar @$trans; }
1521         if($api !~ /fleshed/o) { return $trans; }
1522
1523         my @resp;
1524         for my $t (@$trans) {
1525                         
1526                 if( $t->xact_type ne 'circulation' ) {
1527                         push @resp, {transaction => $t};
1528                         next;
1529                 }
1530
1531                 my $circ = $apputils->simple_scalar_request(
1532                                 "open-ils.cstore",
1533                                 "open-ils.cstore.direct.action.circulation.retrieve",
1534                                 $t->id );
1535
1536                 next unless $circ;
1537
1538                 my $title = $apputils->simple_scalar_request(
1539                         "open-ils.storage", 
1540                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1541                         $circ->target_copy );
1542
1543                 next unless $title;
1544
1545                 my $u = OpenILS::Utils::ModsParser->new();
1546                 $u->start_mods_batch($title->marc());
1547                 my $mods = $u->finish_mods_batch();
1548                 $mods->doc_id($title->id) if $mods;
1549
1550                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1551
1552         }
1553
1554         return \@resp; 
1555
1556
1557
1558 __PACKAGE__->register_method(
1559         method  => "user_transaction_retrieve",
1560         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1561         argc            => 1,
1562         notes           => <<"  NOTES");
1563         Returns a fleshedtransaction record
1564         NOTES
1565 __PACKAGE__->register_method(
1566         method  => "user_transaction_retrieve",
1567         api_name        => "open-ils.actor.user.transaction.retrieve",
1568         argc            => 1,
1569         notes           => <<"  NOTES");
1570         Returns a transaction record
1571         NOTES
1572 sub user_transaction_retrieve {
1573         my( $self, $client, $login_session, $bill_id ) = @_;
1574
1575         # XXX I think I'm deprecated... make sure
1576
1577         my $trans = $apputils->simple_scalar_request( 
1578                 "open-ils.cstore",
1579                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1580                 $bill_id
1581         );
1582
1583         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1584                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1585         return $evt if $evt;
1586         
1587         my $api = $self->api_name();
1588         if($api !~ /fleshed/o) { return $trans; }
1589
1590         if( $trans->xact_type ne 'circulation' ) {
1591                 $logger->debug("Returning non-circ transaction");
1592                 return {transaction => $trans};
1593         }
1594
1595         my $circ = $apputils->simple_scalar_request(
1596                         "open-ils.cstore",
1597                         "open-ils..direct.action.circulation.retrieve",
1598                         $trans->id );
1599
1600         return {transaction => $trans} unless $circ;
1601         $logger->debug("Found the circ transaction");
1602
1603         my $title = $apputils->simple_scalar_request(
1604                 "open-ils.storage", 
1605                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1606                 $circ->target_copy );
1607
1608         return {transaction => $trans, circ => $circ } unless $title;
1609         $logger->debug("Found the circ title");
1610
1611         my $mods;
1612         try {
1613                 my $u = OpenILS::Utils::ModsParser->new();
1614                 $u->start_mods_batch($title->marc());
1615                 $mods = $u->finish_mods_batch();
1616         } otherwise {
1617                 if ($title->id == OILS_PRECAT_RECORD) {
1618                         my $copy = $apputils->simple_scalar_request(
1619                                 "open-ils.cstore",
1620                                 "open-ils.cstore.direct.asset.copy.retrieve",
1621                                 $circ->target_copy );
1622
1623                         $mods = new Fieldmapper::metabib::virtual_record;
1624                         $mods->doc_id(OILS_PRECAT_RECORD);
1625                         $mods->title($copy->dummy_title);
1626                         $mods->author($copy->dummy_author);
1627                 }
1628         };
1629
1630         $logger->debug("MODSized the circ title");
1631
1632         return {transaction => $trans, circ => $circ, record => $mods };
1633 }
1634
1635
1636 __PACKAGE__->register_method(
1637         method  => "hold_request_count",
1638         api_name        => "open-ils.actor.user.hold_requests.count",
1639         argc            => 1,
1640         notes           => <<"  NOTES");
1641         Returns hold ready/total counts
1642         NOTES
1643 sub hold_request_count {
1644         my( $self, $client, $login_session, $userid ) = @_;
1645
1646         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1647                 $login_session, $userid, 'VIEW_HOLD' );
1648         return $evt if $evt;
1649         
1650
1651         my $holds = $apputils->simple_scalar_request(
1652                         "open-ils.cstore",
1653                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1654                         { 
1655                                 usr => $userid,
1656                                 fulfillment_time => {"=" => undef },
1657                                 cancel_time => undef,
1658                         }
1659         );
1660
1661         my @ready;
1662         for my $h (@$holds) {
1663                 next unless $h->capture_time and $h->current_copy;
1664
1665                 my $copy = $apputils->simple_scalar_request(
1666                         "open-ils.cstore",
1667                         "open-ils.cstore.direct.asset.copy.retrieve",
1668                         $h->current_copy
1669                 );
1670
1671                 if ($copy and $copy->status == 8) {
1672                         push @ready, $h;
1673                 }
1674         }
1675
1676         return { total => scalar(@$holds), ready => scalar(@ready) };
1677 }
1678
1679
1680 __PACKAGE__->register_method(
1681         method  => "checkedout_count",
1682         api_name        => "open-ils.actor.user.checked_out.count__",
1683         argc            => 1,
1684         notes           => <<"  NOTES");
1685         Returns a transaction record
1686         NOTES
1687
1688 # XXX Deprecate Me
1689 sub checkedout_count {
1690         my( $self, $client, $login_session, $userid ) = @_;
1691
1692         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1693                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1694         return $evt if $evt;
1695         
1696         my $circs = $apputils->simple_scalar_request(
1697                         "open-ils.cstore",
1698                         "open-ils.cstore.direct.action.circulation.search.atomic",
1699                         { usr => $userid, stop_fines => undef }
1700                         #{ usr => $userid, checkin_time => {"=" => undef } }
1701         );
1702
1703         my $parser = DateTime::Format::ISO8601->new;
1704
1705         my (@out,@overdue);
1706         for my $c (@$circs) {
1707                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1708                 my $due = $due_dt->epoch;
1709
1710                 if ($due < DateTime->today->epoch) {
1711                         push @overdue, $c;
1712                 }
1713         }
1714
1715         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1716 }
1717
1718
1719 __PACKAGE__->register_method(
1720         method          => "checked_out",
1721         api_name                => "open-ils.actor.user.checked_out",
1722         argc                    => 2,
1723         signature       => q/
1724                 Returns a structure of circulations objects sorted by
1725                 out, overdue, lost, claims_returned, long_overdue.
1726                 A list of IDs are returned of each type.
1727                 lost, long_overdue, and claims_returned circ will not
1728                 be "finished" (there is an outstanding balance or some 
1729                 other pending action on the circ). 
1730
1731                 The .count method also includes a 'total' field which 
1732                 sums all "open" circs
1733         /
1734 );
1735
1736 __PACKAGE__->register_method(
1737         method          => "checked_out",
1738         api_name                => "open-ils.actor.user.checked_out.count",
1739         argc                    => 2,
1740         signature       => q/@see open-ils.actor.user.checked_out/
1741 );
1742
1743 sub checked_out {
1744         my( $self, $conn, $auth, $userid ) = @_;
1745
1746         my $e = new_editor(authtoken=>$auth);
1747         return $e->event unless $e->checkauth;
1748
1749         if( $userid ne $e->requestor->id ) {
1750                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1751         }
1752
1753         my $count = $self->api_name =~ /count/;
1754         return _checked_out( $count, $e, $userid );
1755 }
1756
1757 sub _checked_out {
1758         my( $iscount, $e, $userid ) = @_;
1759
1760
1761         my $meth = 'open-ils.storage.actor.user.checked_out';
1762         $meth = "$meth.count" if $iscount;
1763         return $U->storagereq($meth, $userid);
1764
1765 # XXX Old code - moved to storage
1766 #------------------------------------------------------------------------------
1767 #------------------------------------------------------------------------------
1768         my $circs = $e->search_action_circulation( 
1769                 { usr => $userid, checkin_time => undef });
1770
1771         my $parser = DateTime::Format::ISO8601->new;
1772
1773         # split the circs up into overdue and not-overdue circs
1774         my (@out,@overdue);
1775         for my $c (@$circs) {
1776                 if( $c->due_date ) {
1777                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1778                         my $due = $due_dt->epoch;
1779                         if ($due < DateTime->today->epoch) {
1780                                 push @overdue, $c;
1781                         } else {
1782                                 push @out, $c;
1783                         }
1784                 } else {
1785                         push @out, $c;
1786                 }
1787         }
1788
1789         my( @open, @od, @lost, @cr, @lo );
1790
1791         while (my $c = shift(@out)) {
1792                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1793                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1794                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1795                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1796         }
1797
1798         while (my $c = shift(@overdue)) {
1799                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1800                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1801                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1802                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1803         }
1804
1805         if( $iscount ) {
1806                 return {
1807                         total           => @open + @od + @lost + @cr + @lo,
1808                         out             => scalar(@open),
1809                         overdue => scalar(@od),
1810                         lost            => scalar(@lost),
1811                         claims_returned => scalar(@cr),
1812                         long_overdue            => scalar(@lo)
1813                 };
1814         }
1815
1816         return {
1817                 out             => \@open,
1818                 overdue => \@od,
1819                 lost            => \@lost,
1820                 claims_returned => \@cr,
1821                 long_overdue            => \@lo
1822         };
1823 }
1824
1825
1826 sub _checked_out_WHAT {
1827         my( $iscount, $e, $userid ) = @_;
1828
1829         my $circs = $e->search_action_circulation( 
1830                 { usr => $userid, stop_fines => undef });
1831
1832         my $mcircs = $e->search_action_circulation( 
1833                 { 
1834                         usr => $userid, 
1835                         checkin_time => undef, 
1836                         xact_finish => undef, 
1837                 });
1838
1839         
1840         push( @$circs, @$mcircs );
1841
1842         my $parser = DateTime::Format::ISO8601->new;
1843
1844         # split the circs up into overdue and not-overdue circs
1845         my (@out,@overdue);
1846         for my $c (@$circs) {
1847                 if( $c->due_date ) {
1848                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1849                         my $due = $due_dt->epoch;
1850                         if ($due < DateTime->today->epoch) {
1851                                 push @overdue, $c->id;
1852                         } else {
1853                                 push @out, $c->id;
1854                         }
1855                 } else {
1856                         push @out, $c->id;
1857                 }
1858         }
1859
1860         # grab all of the lost, claims-returned, and longoverdue circs
1861         #my $open = $e->search_action_circulation(
1862         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1863
1864
1865         # these items have stop_fines, but no xact_finish, so money
1866         # is owed on them and they have not been checked in
1867         my $open = $e->search_action_circulation(
1868                 {
1869                         usr                             => $userid, 
1870                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1871                         xact_finish             => undef,
1872                         checkin_time    => undef,
1873                 }
1874         );
1875
1876
1877         my( @lost, @cr, @lo );
1878         for my $c (@$open) {
1879                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1880                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1881                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1882         }
1883
1884
1885         if( $iscount ) {
1886                 return {
1887                         total           => @$circs + @lost + @cr + @lo,
1888                         out             => scalar(@out),
1889                         overdue => scalar(@overdue),
1890                         lost            => scalar(@lost),
1891                         claims_returned => scalar(@cr),
1892                         long_overdue            => scalar(@lo)
1893                 };
1894         }
1895
1896         return {
1897                 out             => \@out,
1898                 overdue => \@overdue,
1899                 lost            => \@lost,
1900                 claims_returned => \@cr,
1901                 long_overdue            => \@lo
1902         };
1903 }
1904
1905
1906
1907 __PACKAGE__->register_method(
1908         method          => "checked_in_with_fines",
1909         api_name                => "open-ils.actor.user.checked_in_with_fines",
1910         argc                    => 2,
1911         signature       => q/@see open-ils.actor.user.checked_out/
1912 );
1913 sub checked_in_with_fines {
1914         my( $self, $conn, $auth, $userid ) = @_;
1915
1916         my $e = new_editor(authtoken=>$auth);
1917         return $e->event unless $e->checkauth;
1918
1919         if( $userid ne $e->requestor->id ) {
1920                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1921         }
1922
1923         # money is owed on these items and they are checked in
1924         my $open = $e->search_action_circulation(
1925                 {
1926                         usr                             => $userid, 
1927                         xact_finish             => undef,
1928                         checkin_time    => { "!=" => undef },
1929                 }
1930         );
1931
1932
1933         my( @lost, @cr, @lo );
1934         for my $c (@$open) {
1935                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1936                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1937                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1938         }
1939
1940         return {
1941                 lost            => \@lost,
1942                 claims_returned => \@cr,
1943                 long_overdue            => \@lo
1944         };
1945 }
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955 __PACKAGE__->register_method(
1956         method  => "user_transaction_history",
1957         api_name        => "open-ils.actor.user.transactions.history",
1958         argc            => 1,
1959         notes           => <<"  NOTES");
1960         Returns a list of billable transaction ids for a user, optionally by type
1961         NOTES
1962 __PACKAGE__->register_method(
1963         method  => "user_transaction_history",
1964         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1965         argc            => 1,
1966         notes           => <<"  NOTES");
1967         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1968         NOTES
1969 __PACKAGE__->register_method(
1970         method  => "user_transaction_history",
1971         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1972         argc            => 1,
1973         notes           => <<"  NOTES");
1974         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1975         NOTES
1976 __PACKAGE__->register_method(
1977         method  => "user_transaction_history",
1978         api_name        => "open-ils.actor.user.transactions.history.still_open",
1979         argc            => 1,
1980         notes           => <<"  NOTES");
1981         Returns a list of billable transaction ids for a user that are not finished
1982         NOTES
1983 __PACKAGE__->register_method(
1984         method  => "user_transaction_history",
1985         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1986         argc            => 1,
1987         notes           => <<"  NOTES");
1988         Returns a list of billable transaction ids for a user that has billings
1989         NOTES
1990
1991
1992
1993 =head old
1994 sub _user_transaction_history {
1995         my( $self, $client, $login_session, $user_id, $type ) = @_;
1996
1997         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1998                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1999         return $evt if $evt;
2000
2001         my $api = $self->api_name();
2002         my @xact;
2003         my @charge;
2004         my @balance;
2005
2006         @xact = (xact_type =>  $type) if(defined($type));
2007         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
2008         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
2009
2010         $logger->debug("searching for transaction history: @xact : @balance, @charge");
2011
2012         my $trans = $apputils->simple_scalar_request( 
2013                 "open-ils.cstore",
2014                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
2015                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
2016
2017         return [ map { $_->id } @$trans ];
2018 }
2019 =cut
2020
2021 =head SEE APPUTILS.PM
2022 sub _make_mbts {
2023         my @xacts = @_;
2024
2025         my @mbts;
2026         for my $x (@xacts) {
2027                 my $s = new Fieldmapper::money::billable_transaction_summary;
2028                 $s->id( $x->id );
2029                 $s->usr( $x->usr );
2030                 $s->xact_start( $x->xact_start );
2031                 $s->xact_finish( $x->xact_finish );
2032
2033                 my $to = 0;
2034                 my $lb = undef;
2035                 for my $b (@{ $x->billings }) {
2036                         next if ($U->is_true($b->voided));
2037                         $to += ($b->amount * 100);
2038                         $lb ||= $b->billing_ts;
2039                         if ($b->billing_ts ge $lb) {
2040                                 $lb = $b->billing_ts;
2041                                 $s->last_billing_note($b->note);
2042                                 $s->last_billing_ts($b->billing_ts);
2043                                 $s->last_billing_type($b->billing_type);
2044                         }
2045                 }
2046
2047                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2048
2049                 my $tp = 0;
2050                 my $lp = undef;
2051                 for my $p (@{ $x->payments }) {
2052                         next if ($U->is_true($p->voided));
2053                         $tp += ($p->amount * 100);
2054                         $lp ||= $p->payment_ts;
2055                         if ($p->payment_ts ge $lp) {
2056                                 $lp = $p->payment_ts;
2057                                 $s->last_payment_note($p->note);
2058                                 $s->last_payment_ts($p->payment_ts);
2059                                 $s->last_payment_type($p->payment_type);
2060                         }
2061                 }
2062                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2063
2064                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2065
2066                 $s->xact_type( 'grocery' ) if ($x->grocery);
2067                 $s->xact_type( 'circulation' ) if ($x->circulation);
2068
2069                 push @mbts, $s;
2070         }
2071
2072         return @mbts;
2073 }
2074 =cut
2075
2076 sub user_transaction_history {
2077         my( $self, $conn, $auth, $userid, $type ) = @_;
2078
2079         # run inside of a transaction to prevent replication delays
2080         my $e = new_editor(xact=>1, authtoken=>$auth);
2081         return $e->die_event unless $e->checkauth;
2082
2083         if( $e->requestor->id ne $userid ) {
2084                 return $e->die_event 
2085                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2086         }
2087
2088         my $api = $self->api_name;
2089         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2090
2091         my @xacts = @{ $e->search_money_billable_transaction(
2092                 [       { usr => $userid, @xact_finish },
2093                         { flesh => 1,
2094                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2095                           order_by => { mbt => 'xact_start DESC' },
2096                         }
2097                 ],
2098       {substream => 1}
2099         ) };
2100
2101         $e->rollback;
2102
2103         #my @mbts = _make_mbts( @xacts );
2104         my @mbts = $U->make_mbts( @xacts );
2105
2106         if(defined($type)) {
2107                 @mbts = grep { $_->xact_type eq $type } @mbts;
2108         }
2109
2110         if($api =~ /have_balance/o) {
2111                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2112         }
2113
2114         if($api =~ /have_charge/o) {
2115                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2116         }
2117
2118         if($api =~ /have_bill/o) {
2119                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2120         }
2121
2122         return [@mbts];
2123 }
2124
2125
2126
2127 __PACKAGE__->register_method(
2128         method  => "user_perms",
2129         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2130         argc            => 1,
2131         notes           => <<"  NOTES");
2132         Returns a list of permissions
2133         NOTES
2134 sub user_perms {
2135         my( $self, $client, $authtoken, $user ) = @_;
2136
2137         my( $staff, $evt ) = $apputils->checkses($authtoken);
2138         return $evt if $evt;
2139
2140         $user ||= $staff->id;
2141
2142         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2143                 return $evt;
2144         }
2145
2146         return $apputils->simple_scalar_request(
2147                 "open-ils.storage",
2148                 "open-ils.storage.permission.user_perms.atomic",
2149                 $user);
2150 }
2151
2152 __PACKAGE__->register_method(
2153         method  => "retrieve_perms",
2154         api_name        => "open-ils.actor.permissions.retrieve",
2155         notes           => <<"  NOTES");
2156         Returns a list of permissions
2157         NOTES
2158 sub retrieve_perms {
2159         my( $self, $client ) = @_;
2160         return $apputils->simple_scalar_request(
2161                 "open-ils.cstore",
2162                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2163                 { id => { '!=' => undef } }
2164         );
2165 }
2166
2167 __PACKAGE__->register_method(
2168         method  => "retrieve_groups",
2169         api_name        => "open-ils.actor.groups.retrieve",
2170         notes           => <<"  NOTES");
2171         Returns a list of user groupss
2172         NOTES
2173 sub retrieve_groups {
2174         my( $self, $client ) = @_;
2175         return new_editor()->retrieve_all_permission_grp_tree();
2176 }
2177
2178 __PACKAGE__->register_method(
2179         method  => "retrieve_org_address",
2180         api_name        => "open-ils.actor.org_unit.address.retrieve",
2181         notes           => <<'  NOTES');
2182         Returns an org_unit address by ID
2183         @param An org_address ID
2184         NOTES
2185 sub retrieve_org_address {
2186         my( $self, $client, $id ) = @_;
2187         return $apputils->simple_scalar_request(
2188                 "open-ils.cstore",
2189                 "open-ils.cstore.direct.actor.org_address.retrieve",
2190                 $id
2191         );
2192 }
2193
2194 __PACKAGE__->register_method(
2195         method  => "retrieve_groups_tree",
2196         api_name        => "open-ils.actor.groups.tree.retrieve",
2197         notes           => <<"  NOTES");
2198         Returns a list of user groups
2199         NOTES
2200 sub retrieve_groups_tree {
2201         my( $self, $client ) = @_;
2202         return new_editor()->search_permission_grp_tree(
2203                 [
2204                         { parent => undef},
2205                         {       
2206                                 flesh                           => -1,
2207                                 flesh_fields    => { pgt => ["children"] }, 
2208                                 order_by                        => { pgt => 'name'}
2209                         }
2210                 ]
2211         )->[0];
2212 }
2213
2214
2215 # turns an org list into an org tree
2216 =head old code
2217 sub build_group_tree {
2218
2219         my( $self, $grplist) = @_;
2220
2221         return $grplist unless ( 
2222                         ref($grplist) and @$grplist > 1 );
2223
2224         my @list = sort { $a->name cmp $b->name } @$grplist;
2225
2226         my $root;
2227         for my $grp (@list) {
2228
2229                 if ($grp and !defined($grp->parent)) {
2230                         $root = $grp;
2231                         next;
2232                 }
2233                 my ($parent) = grep { $_->id == $grp->parent} @list;
2234
2235                 $parent->children([]) unless defined($parent->children); 
2236                 push( @{$parent->children}, $grp );
2237         }
2238
2239         return $root;
2240 }
2241 =cut
2242
2243
2244 __PACKAGE__->register_method(
2245         method  => "add_user_to_groups",
2246         api_name        => "open-ils.actor.user.set_groups",
2247         notes           => <<"  NOTES");
2248         Adds a user to one or more permission groups
2249         NOTES
2250
2251 sub add_user_to_groups {
2252         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2253
2254         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2255                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2256         return $evt if $evt;
2257
2258         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2259                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2260         return $evt if $evt;
2261
2262         $apputils->simplereq(
2263                 'open-ils.storage',
2264                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2265                 
2266         for my $group (@$groups) {
2267                 my $link = Fieldmapper::permission::usr_grp_map->new;
2268                 $link->grp($group);
2269                 $link->usr($userid);
2270
2271                 my $id = $apputils->simplereq(
2272                         'open-ils.storage',
2273                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2274         }
2275
2276         return 1;
2277 }
2278
2279 __PACKAGE__->register_method(
2280         method  => "get_user_perm_groups",
2281         api_name        => "open-ils.actor.user.get_groups",
2282         notes           => <<"  NOTES");
2283         Retrieve a user's permission groups.
2284         NOTES
2285
2286
2287 sub get_user_perm_groups {
2288         my( $self, $client, $authtoken, $userid ) = @_;
2289
2290         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2291                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2292         return $evt if $evt;
2293
2294         return $apputils->simplereq(
2295                 'open-ils.cstore',
2296                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2297 }       
2298
2299
2300 __PACKAGE__->register_method(
2301         method  => "get_user_work_ous",
2302         api_name        => "open-ils.actor.user.get_work_ous",
2303         notes           => <<"  NOTES");
2304         Retrieve a user's work org units.
2305         NOTES
2306 __PACKAGE__->register_method(
2307         method  => "get_user_work_ous",
2308         api_name        => "open-ils.actor.user.get_work_ous.ids",
2309         notes           => <<"  NOTES");
2310         Retrieve a user's work org units.
2311         NOTES
2312
2313
2314 sub get_user_work_ous {
2315         my( $self, $client, $auth, $userid ) = @_;
2316     my $e = new_editor(authtoken=>$auth);
2317     return $e->event unless $e->checkauth;
2318     $userid ||= $e->requestor->id;
2319
2320     if($e->requestor->id != $userid) {
2321         my $user = $e->retrieve_actor_user($userid)
2322             or return $e->event;
2323         return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2324     }
2325
2326     return $e->search_permission_usr_work_ou_map({usr => $userid})
2327         unless $self->api_name =~ /.ids$/;
2328
2329     # client just wants a list of org IDs
2330     return $U->get_user_work_ou_ids($e, $userid);
2331 }       
2332
2333
2334
2335
2336 __PACKAGE__->register_method (
2337         method          => 'register_workstation',
2338         api_name                => 'open-ils.actor.workstation.register.override',
2339         signature       => q/@see open-ils.actor.workstation.register/);
2340
2341 __PACKAGE__->register_method (
2342         method          => 'register_workstation',
2343         api_name                => 'open-ils.actor.workstation.register',
2344         signature       => q/
2345                 Registers a new workstion in the system
2346                 @param authtoken The login session key
2347                 @param name The name of the workstation id
2348                 @param owner The org unit that owns this workstation
2349                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2350                 if the name is already in use.
2351         /);
2352
2353 sub register_workstation {
2354         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2355
2356         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2357         return $e->die_event unless $e->checkauth;
2358         return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2359         my $existing = $e->search_actor_workstation({name => $name})->[0];
2360
2361         if( $existing ) {
2362
2363                 if( $self->api_name =~ /override/o ) {
2364             # workstation with the given name exists.  
2365
2366             if($owner ne $existing->owning_lib) {
2367                 # if necessary, update the owning_lib of the workstation
2368
2369                 $logger->info("changing owning lib of workstation ".$existing->id.
2370                     " from ".$existing->owning_lib." to $owner");
2371                             return $e->die_event unless 
2372                     $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib); 
2373
2374                             return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner); 
2375
2376                 $existing->owning_lib($owner);
2377                             return $e->die_event unless $e->update_actor_workstation($existing);
2378
2379                 $e->commit;
2380
2381             } else {
2382                 $logger->info(  
2383                     "attempt to register an existing workstation.  returning existing ID");
2384             }
2385
2386             return $existing->id;
2387
2388                 } else {
2389                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2390                 }
2391         }
2392
2393         my $ws = Fieldmapper::actor::workstation->new;
2394         $ws->owning_lib($owner);
2395         $ws->name($name);
2396         $e->create_actor_workstation($ws) or return $e->die_event;
2397         $e->commit;
2398         return $ws->id; # note: editor sets the id on the new object for us
2399 }
2400
2401 __PACKAGE__->register_method (
2402         method          => 'workstation_list',
2403         api_name                => 'open-ils.actor.workstation.list',
2404         signature       => q/
2405                 Returns a list of workstations registered at the given location
2406                 @param authtoken The login session key
2407                 @param ids A list of org_unit.id's for the workstation owners
2408         /);
2409
2410 sub workstation_list {
2411         my( $self, $conn, $authtoken, @orgs ) = @_;
2412
2413         my $e = new_editor(authtoken=>$authtoken);
2414         return $e->event unless $e->checkauth;
2415     my %results;
2416
2417     for my $o (@orgs) {
2418             return $e->event 
2419             unless $e->allowed('REGISTER_WORKSTATION', $o);
2420         $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2421     }
2422     return \%results;
2423 }
2424
2425
2426
2427
2428
2429
2430
2431 __PACKAGE__->register_method (
2432         method          => 'fetch_patron_note',
2433         api_name                => 'open-ils.actor.note.retrieve.all',
2434         signature       => q/
2435                 Returns a list of notes for a given user
2436                 Requestor must have VIEW_USER permission if pub==false and
2437                 @param authtoken The login session key
2438                 @param args Hash of params including
2439                         patronid : the patron's id
2440                         pub : true if retrieving only public notes
2441         /
2442 );
2443
2444 sub fetch_patron_note {
2445         my( $self, $conn, $authtoken, $args ) = @_;
2446         my $patronid = $$args{patronid};
2447
2448         my($reqr, $evt) = $U->checkses($authtoken);
2449         return $evt if $evt;
2450
2451         my $patron;
2452         ($patron, $evt) = $U->fetch_user($patronid);
2453         return $evt if $evt;
2454
2455         if($$args{pub}) {
2456                 if( $patronid ne $reqr->id ) {
2457                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2458                         return $evt if $evt;
2459                 }
2460                 return $U->cstorereq(
2461                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2462                         { usr => $patronid, pub => 't' } );
2463         }
2464
2465         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2466         return $evt if $evt;
2467
2468         return $U->cstorereq(
2469                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2470 }
2471
2472 __PACKAGE__->register_method (
2473         method          => 'create_user_note',
2474         api_name                => 'open-ils.actor.note.create',
2475         signature       => q/
2476                 Creates a new note for the given user
2477                 @param authtoken The login session key
2478                 @param note The note object
2479         /
2480 );
2481 sub create_user_note {
2482         my( $self, $conn, $authtoken, $note ) = @_;
2483         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2484         return $e->die_event unless $e->checkauth;
2485
2486         my $user = $e->retrieve_actor_user($note->usr)
2487                 or return $e->die_event;
2488
2489         return $e->die_event unless 
2490                 $e->allowed('UPDATE_USER',$user->home_ou);
2491
2492         $note->creator($e->requestor->id);
2493         $e->create_actor_usr_note($note) or return $e->die_event;
2494         $e->commit;
2495         return $note->id;
2496 }
2497
2498
2499 __PACKAGE__->register_method (
2500         method          => 'delete_user_note',
2501         api_name                => 'open-ils.actor.note.delete',
2502         signature       => q/
2503                 Deletes a note for the given user
2504                 @param authtoken The login session key
2505                 @param noteid The note id
2506         /
2507 );
2508 sub delete_user_note {
2509         my( $self, $conn, $authtoken, $noteid ) = @_;
2510
2511         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2512         return $e->die_event unless $e->checkauth;
2513         my $note = $e->retrieve_actor_usr_note($noteid)
2514                 or return $e->die_event;
2515         my $user = $e->retrieve_actor_user($note->usr)
2516                 or return $e->die_event;
2517         return $e->die_event unless 
2518                 $e->allowed('UPDATE_USER', $user->home_ou);
2519         
2520         $e->delete_actor_usr_note($note) or return $e->die_event;
2521         $e->commit;
2522         return 1;
2523 }
2524
2525
2526 __PACKAGE__->register_method (
2527         method          => 'update_user_note',
2528         api_name                => 'open-ils.actor.note.update',
2529         signature       => q/
2530                 @param authtoken The login session key
2531                 @param note The note
2532         /
2533 );
2534
2535 sub update_user_note {
2536         my( $self, $conn, $auth, $note ) = @_;
2537         my $e = new_editor(authtoken=>$auth, xact=>1);
2538         return $e->event unless $e->checkauth;
2539         my $patron = $e->retrieve_actor_user($note->usr)
2540                 or return $e->event;
2541         return $e->event unless 
2542                 $e->allowed('UPDATE_USER', $patron->home_ou);
2543         $e->update_actor_user_note($note)
2544                 or return $e->event;
2545         $e->commit;
2546         return 1;
2547 }
2548
2549
2550
2551
2552 __PACKAGE__->register_method (
2553         method          => 'create_closed_date',
2554         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2555         signature       => q/
2556                 Creates a new closing entry for the given org_unit
2557                 @param authtoken The login session key
2558                 @param note The closed_date object
2559         /
2560 );
2561 sub create_closed_date {
2562         my( $self, $conn, $authtoken, $cd ) = @_;
2563
2564         my( $user, $evt ) = $U->checkses($authtoken);
2565         return $evt if $evt;
2566
2567         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2568         return $evt if $evt;
2569
2570         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2571
2572         my $id = $U->storagereq(
2573                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2574         return $U->DB_UPDATE_FAILED($cd) unless $id;
2575         return $id;
2576 }
2577
2578
2579 __PACKAGE__->register_method (
2580         method          => 'delete_closed_date',
2581         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2582         signature       => q/
2583                 Deletes a closing entry for the given org_unit
2584                 @param authtoken The login session key
2585                 @param noteid The close_date id
2586         /
2587 );
2588 sub delete_closed_date {
2589         my( $self, $conn, $authtoken, $cd ) = @_;
2590
2591         my( $user, $evt ) = $U->checkses($authtoken);
2592         return $evt if $evt;
2593
2594         my $cd_obj;
2595         ($cd_obj, $evt) = fetch_closed_date($cd);
2596         return $evt if $evt;
2597
2598         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2599         return $evt if $evt;
2600
2601         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2602
2603         my $stat = $U->storagereq(
2604                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2605         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2606         return $stat;
2607 }
2608
2609
2610 __PACKAGE__->register_method(
2611         method => 'usrname_exists',
2612         api_name        => 'open-ils.actor.username.exists',
2613         signature => q/
2614                 Returns 1 if the requested username exists, returns 0 otherwise
2615         /
2616 );
2617
2618 # XXX Make me retun undef if no user has the ID 
2619
2620 sub usrname_exists {
2621         my( $self, $conn, $auth, $usrname ) = @_;
2622         my $e = new_editor(authtoken=>$auth);
2623         return $e->event unless $e->checkauth;
2624         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2625         return $$a[0] if $a and @$a;
2626         return 0;
2627 }
2628
2629 __PACKAGE__->register_method(
2630         method => 'barcode_exists',
2631         api_name        => 'open-ils.actor.barcode.exists',
2632         signature => q/
2633                 Returns 1 if the requested barcode exists, returns 0 otherwise
2634         /
2635 );
2636
2637 sub barcode_exists {
2638         my( $self, $conn, $auth, $barcode ) = @_;
2639         my $e = new_editor(authtoken=>$auth);
2640         return $e->event unless $e->checkauth;
2641         my $card = $e->search_actor_card({barcode => $barcode});
2642     return 0 unless @$card;
2643     return $card->[0]->usr;
2644 }
2645
2646
2647 __PACKAGE__->register_method(
2648         method => 'retrieve_net_levels',
2649         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2650 );
2651
2652 sub retrieve_net_levels {
2653         my( $self, $conn, $auth ) = @_;
2654         my $e = new_editor(authtoken=>$auth);
2655         return $e->event unless $e->checkauth;
2656         return $e->retrieve_all_config_net_access_level();
2657 }
2658
2659
2660 __PACKAGE__->register_method(
2661         method => 'fetch_org_by_shortname',
2662         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2663 );
2664 sub fetch_org_by_shortname {
2665         my( $self, $conn, $sname ) = @_;
2666         my $e = new_editor();
2667         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2668         return $e->event unless $org;
2669         return $org;
2670 }
2671
2672
2673 __PACKAGE__->register_method(
2674         method => 'session_home_lib',
2675         api_name => 'open-ils.actor.session.home_lib',
2676 );
2677
2678 sub session_home_lib {
2679         my( $self, $conn, $auth ) = @_;
2680         my $e = new_editor(authtoken=>$auth);
2681         return undef unless $e->checkauth;
2682         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2683         return $org->shortname;
2684 }
2685
2686 __PACKAGE__->register_method(
2687         method => 'session_safe_token',
2688         api_name => 'open-ils.actor.session.safe_token',
2689         signature => q/
2690                 Returns a hashed session ID that is safe for export to the world.
2691                 This safe token will expire after 1 hour of non-use.
2692                 @param auth Active authentication token
2693         /
2694 );
2695
2696 sub session_safe_token {
2697         my( $self, $conn, $auth ) = @_;
2698         my $e = new_editor(authtoken=>$auth);
2699         return undef unless $e->checkauth;
2700
2701         my $safe_token = md5_hex($auth);
2702
2703         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2704
2705         # Add more like the following if needed...
2706         $cache->put_cache(
2707                 "safe-token-home_lib-shortname-$safe_token",
2708                 $e->retrieve_actor_org_unit(
2709                         $e->requestor->home_ou
2710                 )->shortname,
2711                 60 * 60
2712         );
2713
2714         return $safe_token;
2715 }
2716
2717
2718 __PACKAGE__->register_method(
2719         method => 'safe_token_home_lib',
2720         api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2721         signature => q/
2722                 Returns the home library shortname from the session
2723                 asscociated with a safe token from generated by
2724                 open-ils.actor.session.safe_token.
2725                 @param safe_token Active safe token
2726         /
2727 );
2728
2729 sub safe_token_home_lib {
2730         my( $self, $conn, $safe_token ) = @_;
2731
2732         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2733         return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2734 }
2735
2736
2737
2738 __PACKAGE__->register_method(
2739         method => 'slim_tree',
2740         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2741 );
2742 sub slim_tree {
2743         my $tree = new_editor()->search_actor_org_unit( 
2744                 [
2745                         {"parent_ou" => undef },
2746                         {
2747                                 flesh                           => -1,
2748                                 flesh_fields    => { aou =>  ['children'] },
2749                                 order_by                        => { aou => 'name'},
2750                                 select                  => { aou => ["id","shortname", "name"]},
2751                         }
2752                 ]
2753         )->[0];
2754
2755         return trim_tree($tree);
2756 }
2757
2758
2759 sub trim_tree {
2760         my $tree = shift;
2761         return undef unless $tree;
2762         my $htree = {
2763                 code => $tree->shortname,
2764                 name => $tree->name,
2765         };
2766         if( $tree->children and @{$tree->children} ) {
2767                 $htree->{children} = [];
2768                 for my $c (@{$tree->children}) {
2769                         push( @{$htree->{children}}, trim_tree($c) );
2770                 }
2771         }
2772
2773         return $htree;
2774 }
2775
2776
2777 __PACKAGE__->register_method(
2778         method  => "update_penalties",
2779         api_name        => "open-ils.actor.user.penalties.update");
2780 sub update_penalties {
2781         my( $self, $conn, $auth, $userid ) = @_;
2782         my $e = new_editor(authtoken=>$auth);
2783         return $e->event unless $e->checkauth;
2784         $U->update_patron_penalties( 
2785                 authtoken => $auth,
2786                 patronid  => $userid,
2787         );
2788         return 1;
2789 }
2790
2791
2792
2793 __PACKAGE__->register_method(
2794         method  => "user_retrieve_fleshed_by_id",
2795         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2796
2797 sub user_retrieve_fleshed_by_id {
2798         my( $self, $client, $auth, $user_id, $fields ) = @_;
2799         my $e = new_editor(authtoken => $auth);
2800         return $e->event unless $e->checkauth;
2801
2802         if( $e->requestor->id != $user_id ) {
2803                 return $e->event unless $e->allowed('VIEW_USER');
2804         }
2805
2806         $fields ||= [
2807                 "cards",
2808                 "card",
2809                 "standing_penalties",
2810                 "addresses",
2811                 "billing_address",
2812                 "mailing_address",
2813                 "stat_cat_entries" ];
2814         return new_flesh_user($user_id, $fields, $e);
2815 }
2816
2817
2818 sub new_flesh_user {
2819
2820         my $id = shift;
2821         my $fields = shift || [];
2822         my $e   = shift || new_editor(xact=>1);
2823
2824         my $user = $e->retrieve_actor_user(
2825         [
2826         $id,
2827         {
2828                 "flesh"                         => 1,
2829                 "flesh_fields" =>  { "au" => $fields }
2830         }
2831         ]
2832         ) or return $e->event;
2833
2834
2835         if( grep { $_ eq 'addresses' } @$fields ) {
2836
2837                 $user->addresses([]) unless @{$user->addresses};
2838         
2839                 if( ref $user->billing_address ) {
2840                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2841                                 push( @{$user->addresses}, $user->billing_address );
2842                         }
2843                 }
2844         
2845                 if( ref $user->mailing_address ) {
2846                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2847                                 push( @{$user->addresses}, $user->mailing_address );
2848                         }
2849                 }
2850         }
2851
2852         $e->rollback;
2853         $user->clear_passwd();
2854         return $user;
2855 }
2856
2857
2858
2859
2860 __PACKAGE__->register_method(
2861         method  => "user_retrieve_parts",
2862         api_name        => "open-ils.actor.user.retrieve.parts",);
2863
2864 sub user_retrieve_parts {
2865         my( $self, $client, $auth, $user_id, $fields ) = @_;
2866         my $e = new_editor(authtoken => $auth);
2867         return $e->event unless $e->checkauth;
2868         if( $e->requestor->id != $user_id ) {
2869                 return $e->event unless $e->allowed('VIEW_USER');
2870         }
2871         my @resp;
2872         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2873         push(@resp, $user->$_()) for(@$fields);
2874         return \@resp;
2875 }
2876
2877
2878
2879 __PACKAGE__->register_method(
2880     method => 'user_opt_in_enabled',
2881     api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2882     signature => q/
2883         @return 1 if user opt-in is globally enabled, 0 otherwise.
2884     /);
2885
2886 sub user_opt_in_enabled {
2887     my($self, $conn) = @_;
2888     my $sc = OpenSRF::Utils::SettingsClient->new;
2889     return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true'; 
2890     return 0;
2891 }
2892     
2893
2894 __PACKAGE__->register_method(
2895     method => 'user_opt_in_at_org',
2896     api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2897     signature => q/
2898         @param $auth The auth token
2899         @param user_id The ID of the user to test
2900         @return 1 if the user has opted in at the specified org,
2901             event on error, and 0 otherwise. /);
2902 sub user_opt_in_at_org {
2903     my($self, $conn, $auth, $user_id) = @_;
2904
2905     # see if we even need to enforce the opt-in value
2906     return 1 unless $self->user_opt_in_enabled;
2907
2908         my $e = new_editor(authtoken => $auth);
2909         return $e->event unless $e->checkauth;
2910     my $org_id = $e->requestor->ws_ou;
2911
2912     my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2913         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2914
2915     # user is automatically opted-in at the home org
2916     return 1 if $user->home_ou eq $org_id;
2917
2918     my $vals = $e->search_actor_usr_org_unit_opt_in(
2919         {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
2920
2921     return 1 if @$vals;
2922     return 0;
2923 }
2924
2925 __PACKAGE__->register_method(
2926     method => 'create_user_opt_in_at_org',
2927     api_name => 'open-ils.actor.user.org_unit_opt_in.create',
2928     signature => q/
2929         @param $auth The auth token
2930         @param user_id The ID of the user to test
2931         @return The ID of the newly created object, event on error./);
2932
2933 sub create_user_opt_in_at_org {
2934     my($self, $conn, $auth, $user_id) = @_;
2935
2936         my $e = new_editor(authtoken => $auth, xact=>1);
2937         return $e->die_event unless $e->checkauth;
2938     my $org_id = $e->requestor->ws_ou;
2939
2940     my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2941         return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2942
2943     my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
2944
2945     $opt_in->org_unit($org_id);
2946     $opt_in->usr($user_id);
2947     $opt_in->staff($e->requestor->id);
2948     $opt_in->opt_in_ts('now');
2949     $opt_in->opt_in_ws($e->requestor->wsid);
2950
2951     $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
2952         or return $e->die_event;
2953
2954     $e->commit;
2955
2956     return $opt_in->id;
2957 }
2958
2959
2960
2961
2962
2963
2964 1;
2965