]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
ported user prefs call to cstoreEditor, with finer grained perm checking
[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, $auth, $user_id, $setting ) = @_;
123
124     my $e = new_editor(authtoken => $auth);
125     return $e->event unless $e->checkauth;
126
127     my $patron = $e->retrieve_actor_user($user_id) or return $e->event;
128     if($e->requestor->id != $user_id) {
129         return $e->event unless $e->allowed('VIEW_USER', $patron->home_ou);
130     }
131
132     my $s = $e->search_actor_user_setting({usr => $user_id});
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     authoritative => 1,
918         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
919
920 sub user_retrieve_by_barcode {
921         my($self, $client, $user_session, $barcode) = @_;
922
923         $logger->debug("Searching for user with barcode $barcode");
924         my ($user_obj, $evt) = $apputils->checkses($user_session);
925         return $evt if $evt;
926
927         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
928                         "open-ils.cstore", 
929                         "open-ils.cstore.direct.actor.card.search.atomic",
930                         { barcode => $barcode }
931         );
932
933         if(!$card || !$card->[0]) {
934                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
935         }
936
937         $card = $card->[0];
938         my $user = flesh_user($card->usr());
939
940         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
941         return $evt if $evt;
942
943         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
944         return $user;
945
946 }
947
948
949
950 __PACKAGE__->register_method(
951         method  => "get_user_by_id",
952         api_name        => "open-ils.actor.user.retrieve",);
953
954 sub get_user_by_id {
955         my ($self, $client, $auth, $id) = @_;
956         my $e = new_editor(authtoken=>$auth);
957         return $e->event unless $e->checkauth;
958         my $user = $e->retrieve_actor_user($id)
959                 or return $e->event;
960         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
961         return $user;
962 }
963
964
965
966 __PACKAGE__->register_method(
967         method  => "get_org_types",
968         api_name        => "open-ils.actor.org_types.retrieve",);
969
970 sub get_org_types {
971     return $U->get_org_types();
972 }
973
974
975
976 __PACKAGE__->register_method(
977         method  => "get_user_ident_types",
978         api_name        => "open-ils.actor.user.ident_types.retrieve",
979 );
980 my $ident_types;
981 sub get_user_ident_types {
982         return $ident_types if $ident_types;
983         return $ident_types = 
984                 new_editor()->retrieve_all_config_identification_type();
985 }
986
987
988
989
990 __PACKAGE__->register_method(
991         method  => "get_org_unit",
992         api_name        => "open-ils.actor.org_unit.retrieve",
993 );
994
995 sub get_org_unit {
996         my( $self, $client, $user_session, $org_id ) = @_;
997         my $e = new_editor(authtoken => $user_session);
998         if(!$org_id) {
999                 return $e->event unless $e->checkauth;
1000                 $org_id = $e->requestor->ws_ou;
1001         }
1002         my $o = $e->retrieve_actor_org_unit($org_id)
1003                 or return $e->event;
1004         return $o;
1005 }
1006
1007 __PACKAGE__->register_method(
1008         method  => "search_org_unit",
1009         api_name        => "open-ils.actor.org_unit_list.search",
1010 );
1011
1012 sub search_org_unit {
1013
1014         my( $self, $client, $field, $value ) = @_;
1015
1016         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1017                 "open-ils.cstore",
1018                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
1019                 { $field => $value } );
1020
1021         return $list;
1022 }
1023
1024
1025 # build the org tree
1026
1027 __PACKAGE__->register_method(
1028         method  => "get_org_tree",
1029         api_name        => "open-ils.actor.org_tree.retrieve",
1030         argc            => 0, 
1031         note            => "Returns the entire org tree structure",
1032 );
1033
1034 sub get_org_tree {
1035         my( $self, $client) = @_;
1036     return $U->get_org_tree();
1037 }
1038
1039
1040 __PACKAGE__->register_method(
1041         method  => "get_org_descendants",
1042         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1043 );
1044
1045 # depth is optional.  org_unit is the id
1046 sub get_org_descendants {
1047         my( $self, $client, $org_unit, $depth ) = @_;
1048         my $orglist = $apputils->simple_scalar_request(
1049                         "open-ils.storage", 
1050                         "open-ils.storage.actor.org_unit.descendants.atomic",
1051                         $org_unit, $depth );
1052         return $U->build_org_tree($orglist);
1053 }
1054
1055
1056 __PACKAGE__->register_method(
1057         method  => "get_org_ancestors",
1058         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1059 );
1060
1061 # depth is optional.  org_unit is the id
1062 sub get_org_ancestors {
1063         my( $self, $client, $org_unit, $depth ) = @_;
1064         my $orglist = $apputils->simple_scalar_request(
1065                         "open-ils.storage", 
1066                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1067                         $org_unit, $depth );
1068         return $U->build_org_tree($orglist);
1069 }
1070
1071
1072 __PACKAGE__->register_method(
1073         method  => "get_standings",
1074         api_name        => "open-ils.actor.standings.retrieve"
1075 );
1076
1077 my $user_standings;
1078 sub get_standings {
1079         return $user_standings if $user_standings;
1080         return $user_standings = 
1081                 $apputils->simple_scalar_request(
1082                         "open-ils.cstore",
1083                         "open-ils.cstore.direct.config.standing.search.atomic",
1084                         { id => { "!=" => undef } }
1085                 );
1086 }
1087
1088
1089
1090 __PACKAGE__->register_method(
1091         method  => "get_my_org_path",
1092         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1093 );
1094
1095 sub get_my_org_path {
1096         my( $self, $client, $auth, $org_id ) = @_;
1097         my $e = new_editor(authtoken=>$auth);
1098         return $e->event unless $e->checkauth;
1099         $org_id = $e->requestor->ws_ou unless defined $org_id;
1100
1101         return $apputils->simple_scalar_request(
1102                 "open-ils.storage",
1103                 "open-ils.storage.actor.org_unit.full_path.atomic",
1104                 $org_id );
1105 }
1106
1107
1108 __PACKAGE__->register_method(
1109         method  => "patron_adv_search",
1110         api_name        => "open-ils.actor.patron.search.advanced" );
1111 sub patron_adv_search {
1112         my( $self, $client, $auth, $search_hash, 
1113         $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1114
1115         my $e = new_editor(authtoken=>$auth);
1116         return $e->event unless $e->checkauth;
1117         return $e->event unless $e->allowed('VIEW_USER');
1118         return $U->storagereq(
1119                 "open-ils.storage.actor.user.crazy_search", $search_hash, 
1120             $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1121 }
1122
1123
1124 __PACKAGE__->register_method(
1125         method  => "update_passwd",
1126     authoritative => 1,
1127         api_name        => "open-ils.actor.user.password.update");
1128
1129 __PACKAGE__->register_method(
1130         method  => "update_passwd",
1131         api_name        => "open-ils.actor.user.username.update");
1132
1133 __PACKAGE__->register_method(
1134         method  => "update_passwd",
1135         api_name        => "open-ils.actor.user.email.update");
1136
1137 sub update_passwd {
1138     my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1139     my $e = new_editor(xact=>1, authtoken=>$auth);
1140     return $e->die_event unless $e->checkauth;
1141
1142     my $db_user = $e->retrieve_actor_user($e->requestor->id)
1143         or return $e->die_event;
1144     my $api = $self->api_name;
1145
1146     if( $api =~ /password/o ) {
1147
1148         # make sure the original password matches the in-database password
1149         return OpenILS::Event->new('INCORRECT_PASSWORD')
1150             if md5_hex($orig_pw) ne $db_user->passwd;
1151         $db_user->passwd($new_val);
1152
1153     } else {
1154
1155         # if we don't clear the password, the user will be updated with
1156         # a hashed version of the hashed version of their password
1157         $db_user->clear_passwd;
1158
1159         if( $api =~ /username/o ) {
1160
1161             # make sure no one else has this username
1162             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
1163                         return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1164             $db_user->usrname($new_val);
1165
1166         } elsif( $api =~ /email/o ) {
1167             $db_user->email($new_val);
1168         }
1169     }
1170
1171     $e->update_actor_user($db_user) or return $e->die_event;
1172     $e->commit;
1173     return 1;
1174 }
1175
1176
1177
1178
1179 __PACKAGE__->register_method(
1180         method  => "check_user_perms",
1181         api_name        => "open-ils.actor.user.perm.check",
1182         notes           => <<"  NOTES");
1183         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1184         perm type, if the user does *not* have the given permission it is added
1185         to a list which is returned from the method.  If all permissions
1186         are allowed, an empty list is returned
1187         if the logged in user does not match 'user_id', then the logged in user must
1188         have VIEW_PERMISSION priveleges.
1189         NOTES
1190
1191 sub check_user_perms {
1192         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1193
1194         my( $staff, $evt ) = $apputils->checkses($login_session);
1195         return $evt if $evt;
1196
1197         if($staff->id ne $user_id) {
1198                 if( $evt = $apputils->check_perms(
1199                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1200                         return $evt;
1201                 }
1202         }
1203
1204         my @not_allowed;
1205         for my $perm (@$perm_types) {
1206                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1207                         push @not_allowed, $perm;
1208                 }
1209         }
1210
1211         return \@not_allowed
1212 }
1213
1214 __PACKAGE__->register_method(
1215         method  => "check_user_perms2",
1216         api_name        => "open-ils.actor.user.perm.check.multi_org",
1217         notes           => q/
1218                 Checks the permissions on a list of perms and orgs for a user
1219                 @param authtoken The login session key
1220                 @param user_id The id of the user to check
1221                 @param orgs The array of org ids
1222                 @param perms The array of permission names
1223                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1224                 if the logged in user does not match 'user_id', then the logged in user must
1225                 have VIEW_PERMISSION priveleges.
1226         /);
1227
1228 sub check_user_perms2 {
1229         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1230
1231         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1232                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1233         return $evt if $evt;
1234
1235         my @not_allowed;
1236         for my $org (@$orgs) {
1237                 for my $perm (@$perms) {
1238                         if($apputils->check_perms($user_id, $org, $perm)) {
1239                                 push @not_allowed, [ $org, $perm ];
1240                         }
1241                 }
1242         }
1243
1244         return \@not_allowed
1245 }
1246
1247
1248 __PACKAGE__->register_method(
1249         method => 'check_user_perms3',
1250         api_name        => 'open-ils.actor.user.perm.highest_org',
1251         notes           => q/
1252                 Returns the highest org unit id at which a user has a given permission
1253                 If the requestor does not match the target user, the requestor must have
1254                 'VIEW_PERMISSION' rights at the home org unit of the target user
1255                 @param authtoken The login session key
1256                 @param userid The id of the user in question
1257                 @param perm The permission to check
1258                 @return The org unit highest in the org tree within which the user has
1259                 the requested permission
1260         /);
1261
1262 sub check_user_perms3 {
1263         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1264
1265         my( $staff, $target, $org, $evt );
1266
1267         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1268                 $authtoken, $userid, 'VIEW_PERMISSION' );
1269         return $evt if $evt;
1270
1271         my $tree = $U->get_org_tree();
1272         return $U->find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1273 }
1274
1275
1276 __PACKAGE__->register_method(
1277         method => 'check_user_work_perms',
1278         api_name        => 'open-ils.actor.user.work_perm.highest_org_set',
1279     authoritative => 1,
1280     signature => {
1281         desc => q/
1282             Returns a set of org units which represent the highest orgs in 
1283             the org tree where the user has the requested permission.  The
1284             purpose of this method is to return the smallest set of org units
1285             which represent the full expanse of the user's ability to perform
1286             the requested action.  The user whose perms this method should
1287             check is implied by the authtoken. /,
1288         params => [
1289                     {desc => 'authtoken', type => 'string'},
1290             {desc => 'permission name', type => 'string'},
1291             {desc => 'options hash, including "descendants", which will include all child orgs of the found perm orgs', type => 'hash'}
1292         ],
1293         return => {desc => 'An array of org IDs'}
1294     }
1295 );
1296
1297 sub check_user_work_perms {
1298     my($self, $conn, $auth, $perm, $options) = @_;
1299     my $e = new_editor(authtoken=>$auth);
1300     return $e->event unless $e->checkauth;
1301     return $U->find_highest_work_orgs($e, $perm, $options);
1302 }
1303
1304 __PACKAGE__->register_method(
1305         method => 'check_user_perms4',
1306         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1307         notes           => q/
1308                 Returns the highest org unit id at which a user has a given permission
1309                 If the requestor does not match the target user, the requestor must have
1310                 'VIEW_PERMISSION' rights at the home org unit of the target user
1311                 @param authtoken The login session key
1312                 @param userid The id of the user in question
1313                 @param perms An array of perm names to check 
1314                 @return An array of orgId's  representing the org unit 
1315                 highest in the org tree within which the user has the requested permission
1316                 The arrah of orgId's has matches the order of the perms array
1317         /);
1318
1319 sub check_user_perms4 {
1320         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1321         
1322         my( $staff, $target, $org, $evt );
1323
1324         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1325                 $authtoken, $userid, 'VIEW_PERMISSION' );
1326         return $evt if $evt;
1327
1328         my @arr;
1329         return [] unless ref($perms);
1330         my $tree = $U->get_org_tree();
1331
1332         for my $p (@$perms) {
1333                 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1334         }
1335         return \@arr;
1336 }
1337
1338
1339
1340
1341 __PACKAGE__->register_method(
1342         method  => "user_fines_summary",
1343         api_name        => "open-ils.actor.user.fines.summary",
1344     authoritative => 1,
1345         notes           => <<"  NOTES");
1346         Returns a short summary of the users total open fines, excluding voided fines
1347         Params are login_session, user_id
1348         Returns a 'mous' object.
1349         NOTES
1350
1351 sub user_fines_summary {
1352         my( $self, $client, $auth, $user_id ) = @_;
1353         my $e = new_editor(authtoken=>$auth);
1354         return $e->event unless $e->checkauth;
1355         my $user = $e->retrieve_actor_user($user_id)
1356                 or return $e->event;
1357
1358         if( $user_id ne $e->requestor->id ) {
1359                 return $e->event unless 
1360                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1361         }
1362         
1363         # run this inside a transaction to prevent replication delay errors
1364         my $ses = $U->start_db_session();
1365         my $s = $ses->request(
1366                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1367         $U->rollback_db_session($ses);
1368         return $s;
1369 }
1370
1371
1372
1373
1374 __PACKAGE__->register_method(
1375         method  => "user_transactions",
1376         api_name        => "open-ils.actor.user.transactions",
1377         notes           => <<"  NOTES");
1378         Returns a list of open user transactions (mbts objects);
1379         Params are login_session, user_id
1380         Optional third parameter is the transactions type.  defaults to all
1381         NOTES
1382
1383 __PACKAGE__->register_method(
1384         method  => "user_transactions",
1385         api_name        => "open-ils.actor.user.transactions.have_charge",
1386         notes           => <<"  NOTES");
1387         Returns a list of all open user transactions (mbts objects) that have an initial charge
1388         Params are login_session, user_id
1389         Optional third parameter is the transactions type.  defaults to all
1390         NOTES
1391
1392 __PACKAGE__->register_method(
1393         method  => "user_transactions",
1394         api_name        => "open-ils.actor.user.transactions.have_balance",
1395         notes           => <<"  NOTES");
1396         Returns a list of all open user transactions (mbts objects) that have a balance
1397         Params are login_session, user_id
1398         Optional third parameter is the transactions type.  defaults to all
1399         NOTES
1400
1401 __PACKAGE__->register_method(
1402         method  => "user_transactions",
1403         api_name        => "open-ils.actor.user.transactions.fleshed",
1404         notes           => <<"  NOTES");
1405         Returns an object/hash of transaction, circ, title where transaction = an open 
1406         user transactions (mbts objects), circ is the attached circluation, and title
1407         is the title the circ points to
1408         Params are login_session, user_id
1409         Optional third parameter is the transactions type.  defaults to all
1410         NOTES
1411
1412 __PACKAGE__->register_method(
1413         method  => "user_transactions",
1414         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1415         notes           => <<"  NOTES");
1416         Returns an object/hash of transaction, circ, title where transaction = an open 
1417         user transactions that has an initial charge (mbts objects), circ is the 
1418         attached circluation, and title is the title the circ points to
1419         Params are login_session, user_id
1420         Optional third parameter is the transactions type.  defaults to all
1421         NOTES
1422
1423 __PACKAGE__->register_method(
1424         method  => "user_transactions",
1425         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1426         notes           => <<"  NOTES");
1427         Returns an object/hash of transaction, circ, title where transaction = an open 
1428         user transaction that has a balance (mbts objects), circ is the attached 
1429         circluation, and title is the title the circ points to
1430         Params are login_session, user_id
1431         Optional third parameter is the transaction type.  defaults to all
1432         NOTES
1433
1434 __PACKAGE__->register_method(
1435         method  => "user_transactions",
1436         api_name        => "open-ils.actor.user.transactions.count",
1437         notes           => <<"  NOTES");
1438         Returns an object/hash of transaction, circ, title where transaction = an open 
1439         user transactions (mbts objects), circ is the attached circluation, and title
1440         is the title the circ points to
1441         Params are login_session, user_id
1442         Optional third parameter is the transactions type.  defaults to all
1443         NOTES
1444
1445 __PACKAGE__->register_method(
1446         method  => "user_transactions",
1447         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1448         notes           => <<"  NOTES");
1449         Returns an object/hash of transaction, circ, title where transaction = an open 
1450         user transactions that has an initial charge (mbts objects), circ is the 
1451         attached circluation, and title is the title the circ points to
1452         Params are login_session, user_id
1453         Optional third parameter is the transactions type.  defaults to all
1454         NOTES
1455
1456 __PACKAGE__->register_method(
1457         method  => "user_transactions",
1458         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1459         notes           => <<"  NOTES");
1460         Returns an object/hash of transaction, circ, title where transaction = an open 
1461         user transaction that has a balance (mbts objects), circ is the attached 
1462         circluation, and title is the title the circ points to
1463         Params are login_session, user_id
1464         Optional third parameter is the transaction type.  defaults to all
1465         NOTES
1466
1467 __PACKAGE__->register_method(
1468         method  => "user_transactions",
1469         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1470         notes           => <<"  NOTES");
1471         Returns an object/hash of transaction, circ, title where transaction = an open 
1472         user transaction that has a balance (mbts objects), circ is the attached 
1473         circluation, and title is the title the circ points to
1474         Params are login_session, user_id
1475         Optional third parameter is the transaction type.  defaults to all
1476         NOTES
1477
1478
1479
1480 sub user_transactions {
1481         my( $self, $client, $login_session, $user_id, $type ) = @_;
1482
1483         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1484                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1485         return $evt if $evt;
1486
1487         my $api = $self->api_name();
1488         my $trans;
1489         my @xact;
1490
1491         if(defined($type)) { @xact = (xact_type =>  $type); 
1492
1493         } else { @xact = (); }
1494
1495         ($trans) = $self
1496                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1497                 ->run($login_session => $user_id => $type);
1498
1499         if($api =~ /have_charge/o) {
1500
1501                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1502
1503         } elsif($api =~ /have_balance/o) {
1504
1505                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1506         } else {
1507
1508                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1509
1510         }
1511
1512         if($api =~ /total/o) { 
1513                 my $total = 0.0;
1514                 for my $t (@$trans) {
1515                         $total += $t->balance_owed;
1516                 }
1517
1518                 $logger->debug("Total balance owed by user $user_id: $total");
1519                 return $total;
1520         }
1521
1522         if($api =~ /count/o) { return scalar @$trans; }
1523         if($api !~ /fleshed/o) { return $trans; }
1524
1525         my @resp;
1526         for my $t (@$trans) {
1527                         
1528                 if( $t->xact_type ne 'circulation' ) {
1529                         push @resp, {transaction => $t};
1530                         next;
1531                 }
1532
1533                 my $circ = $apputils->simple_scalar_request(
1534                                 "open-ils.cstore",
1535                                 "open-ils.cstore.direct.action.circulation.retrieve",
1536                                 $t->id );
1537
1538                 next unless $circ;
1539
1540                 my $title = $apputils->simple_scalar_request(
1541                         "open-ils.storage", 
1542                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1543                         $circ->target_copy );
1544
1545                 next unless $title;
1546
1547                 my $u = OpenILS::Utils::ModsParser->new();
1548                 $u->start_mods_batch($title->marc());
1549                 my $mods = $u->finish_mods_batch();
1550                 $mods->doc_id($title->id) if $mods;
1551
1552                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1553
1554         }
1555
1556         return \@resp; 
1557
1558
1559
1560 __PACKAGE__->register_method(
1561         method  => "user_transaction_retrieve",
1562         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1563         argc            => 1,
1564         notes           => <<"  NOTES");
1565         Returns a fleshedtransaction record
1566         NOTES
1567 __PACKAGE__->register_method(
1568         method  => "user_transaction_retrieve",
1569         api_name        => "open-ils.actor.user.transaction.retrieve",
1570         argc            => 1,
1571         notes           => <<"  NOTES");
1572         Returns a transaction record
1573         NOTES
1574 sub user_transaction_retrieve {
1575         my( $self, $client, $login_session, $bill_id ) = @_;
1576
1577         # XXX I think I'm deprecated... make sure
1578
1579         my $trans = $apputils->simple_scalar_request( 
1580                 "open-ils.cstore",
1581                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1582                 $bill_id
1583         );
1584
1585         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1586                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1587         return $evt if $evt;
1588         
1589         my $api = $self->api_name();
1590         if($api !~ /fleshed/o) { return $trans; }
1591
1592         if( $trans->xact_type ne 'circulation' ) {
1593                 $logger->debug("Returning non-circ transaction");
1594                 return {transaction => $trans};
1595         }
1596
1597         my $circ = $apputils->simple_scalar_request(
1598                         "open-ils.cstore",
1599                         "open-ils..direct.action.circulation.retrieve",
1600                         $trans->id );
1601
1602         return {transaction => $trans} unless $circ;
1603         $logger->debug("Found the circ transaction");
1604
1605         my $title = $apputils->simple_scalar_request(
1606                 "open-ils.storage", 
1607                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1608                 $circ->target_copy );
1609
1610         return {transaction => $trans, circ => $circ } unless $title;
1611         $logger->debug("Found the circ title");
1612
1613         my $mods;
1614         try {
1615                 my $u = OpenILS::Utils::ModsParser->new();
1616                 $u->start_mods_batch($title->marc());
1617                 $mods = $u->finish_mods_batch();
1618         } otherwise {
1619                 if ($title->id == OILS_PRECAT_RECORD) {
1620                         my $copy = $apputils->simple_scalar_request(
1621                                 "open-ils.cstore",
1622                                 "open-ils.cstore.direct.asset.copy.retrieve",
1623                                 $circ->target_copy );
1624
1625                         $mods = new Fieldmapper::metabib::virtual_record;
1626                         $mods->doc_id(OILS_PRECAT_RECORD);
1627                         $mods->title($copy->dummy_title);
1628                         $mods->author($copy->dummy_author);
1629                 }
1630         };
1631
1632         $logger->debug("MODSized the circ title");
1633
1634         return {transaction => $trans, circ => $circ, record => $mods };
1635 }
1636
1637
1638 __PACKAGE__->register_method(
1639         method  => "hold_request_count",
1640         api_name        => "open-ils.actor.user.hold_requests.count",
1641     authoritative => 1,
1642         argc            => 1,
1643         notes           => <<"  NOTES");
1644         Returns hold ready/total counts
1645         NOTES
1646 sub hold_request_count {
1647         my( $self, $client, $login_session, $userid ) = @_;
1648
1649         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1650                 $login_session, $userid, 'VIEW_HOLD' );
1651         return $evt if $evt;
1652         
1653
1654         my $holds = $apputils->simple_scalar_request(
1655                         "open-ils.cstore",
1656                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1657                         { 
1658                                 usr => $userid,
1659                                 fulfillment_time => {"=" => undef },
1660                                 cancel_time => undef,
1661                         }
1662         );
1663
1664         my @ready;
1665         for my $h (@$holds) {
1666                 next unless $h->capture_time and $h->current_copy;
1667
1668                 my $copy = $apputils->simple_scalar_request(
1669                         "open-ils.cstore",
1670                         "open-ils.cstore.direct.asset.copy.retrieve",
1671                         $h->current_copy
1672                 );
1673
1674                 if ($copy and $copy->status == 8) {
1675                         push @ready, $h;
1676                 }
1677         }
1678
1679         return { total => scalar(@$holds), ready => scalar(@ready) };
1680 }
1681
1682
1683 __PACKAGE__->register_method(
1684         method  => "checkedout_count",
1685         api_name        => "open-ils.actor.user.checked_out.count__",
1686         argc            => 1,
1687         notes           => <<"  NOTES");
1688         Returns a transaction record
1689         NOTES
1690
1691 # XXX Deprecate Me
1692 sub checkedout_count {
1693         my( $self, $client, $login_session, $userid ) = @_;
1694
1695         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1696                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1697         return $evt if $evt;
1698         
1699         my $circs = $apputils->simple_scalar_request(
1700                         "open-ils.cstore",
1701                         "open-ils.cstore.direct.action.circulation.search.atomic",
1702                         { usr => $userid, stop_fines => undef }
1703                         #{ usr => $userid, checkin_time => {"=" => undef } }
1704         );
1705
1706         my $parser = DateTime::Format::ISO8601->new;
1707
1708         my (@out,@overdue);
1709         for my $c (@$circs) {
1710                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1711                 my $due = $due_dt->epoch;
1712
1713                 if ($due < DateTime->today->epoch) {
1714                         push @overdue, $c;
1715                 }
1716         }
1717
1718         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1719 }
1720
1721
1722 __PACKAGE__->register_method(
1723         method          => "checked_out",
1724         api_name                => "open-ils.actor.user.checked_out",
1725     authoritative => 1,
1726         argc                    => 2,
1727         signature       => q/
1728                 Returns a structure of circulations objects sorted by
1729                 out, overdue, lost, claims_returned, long_overdue.
1730                 A list of IDs are returned of each type.
1731                 lost, long_overdue, and claims_returned circ will not
1732                 be "finished" (there is an outstanding balance or some 
1733                 other pending action on the circ). 
1734
1735                 The .count method also includes a 'total' field which 
1736                 sums all "open" circs
1737         /
1738 );
1739
1740 __PACKAGE__->register_method(
1741         method          => "checked_out",
1742         api_name                => "open-ils.actor.user.checked_out.count",
1743     authoritative => 1,
1744         argc                    => 2,
1745         signature       => q/@see open-ils.actor.user.checked_out/
1746 );
1747
1748 sub checked_out {
1749         my( $self, $conn, $auth, $userid ) = @_;
1750
1751         my $e = new_editor(authtoken=>$auth);
1752         return $e->event unless $e->checkauth;
1753
1754         if( $userid ne $e->requestor->id ) {
1755                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1756         }
1757
1758         my $count = $self->api_name =~ /count/;
1759         return _checked_out( $count, $e, $userid );
1760 }
1761
1762 sub _checked_out {
1763         my( $iscount, $e, $userid ) = @_;
1764
1765
1766         my $meth = 'open-ils.storage.actor.user.checked_out';
1767         $meth = "$meth.count" if $iscount;
1768         return $U->storagereq($meth, $userid);
1769
1770 # XXX Old code - moved to storage
1771 #------------------------------------------------------------------------------
1772 #------------------------------------------------------------------------------
1773         my $circs = $e->search_action_circulation( 
1774                 { usr => $userid, checkin_time => undef });
1775
1776         my $parser = DateTime::Format::ISO8601->new;
1777
1778         # split the circs up into overdue and not-overdue circs
1779         my (@out,@overdue);
1780         for my $c (@$circs) {
1781                 if( $c->due_date ) {
1782                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1783                         my $due = $due_dt->epoch;
1784                         if ($due < DateTime->today->epoch) {
1785                                 push @overdue, $c;
1786                         } else {
1787                                 push @out, $c;
1788                         }
1789                 } else {
1790                         push @out, $c;
1791                 }
1792         }
1793
1794         my( @open, @od, @lost, @cr, @lo );
1795
1796         while (my $c = shift(@out)) {
1797                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1798                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1799                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1800                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1801         }
1802
1803         while (my $c = shift(@overdue)) {
1804                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1805                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1806                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1807                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1808         }
1809
1810         if( $iscount ) {
1811                 return {
1812                         total           => @open + @od + @lost + @cr + @lo,
1813                         out             => scalar(@open),
1814                         overdue => scalar(@od),
1815                         lost            => scalar(@lost),
1816                         claims_returned => scalar(@cr),
1817                         long_overdue            => scalar(@lo)
1818                 };
1819         }
1820
1821         return {
1822                 out             => \@open,
1823                 overdue => \@od,
1824                 lost            => \@lost,
1825                 claims_returned => \@cr,
1826                 long_overdue            => \@lo
1827         };
1828 }
1829
1830
1831 sub _checked_out_WHAT {
1832         my( $iscount, $e, $userid ) = @_;
1833
1834         my $circs = $e->search_action_circulation( 
1835                 { usr => $userid, stop_fines => undef });
1836
1837         my $mcircs = $e->search_action_circulation( 
1838                 { 
1839                         usr => $userid, 
1840                         checkin_time => undef, 
1841                         xact_finish => undef, 
1842                 });
1843
1844         
1845         push( @$circs, @$mcircs );
1846
1847         my $parser = DateTime::Format::ISO8601->new;
1848
1849         # split the circs up into overdue and not-overdue circs
1850         my (@out,@overdue);
1851         for my $c (@$circs) {
1852                 if( $c->due_date ) {
1853                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1854                         my $due = $due_dt->epoch;
1855                         if ($due < DateTime->today->epoch) {
1856                                 push @overdue, $c->id;
1857                         } else {
1858                                 push @out, $c->id;
1859                         }
1860                 } else {
1861                         push @out, $c->id;
1862                 }
1863         }
1864
1865         # grab all of the lost, claims-returned, and longoverdue circs
1866         #my $open = $e->search_action_circulation(
1867         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1868
1869
1870         # these items have stop_fines, but no xact_finish, so money
1871         # is owed on them and they have not been checked in
1872         my $open = $e->search_action_circulation(
1873                 {
1874                         usr                             => $userid, 
1875                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1876                         xact_finish             => undef,
1877                         checkin_time    => undef,
1878                 }
1879         );
1880
1881
1882         my( @lost, @cr, @lo );
1883         for my $c (@$open) {
1884                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1885                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1886                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1887         }
1888
1889
1890         if( $iscount ) {
1891                 return {
1892                         total           => @$circs + @lost + @cr + @lo,
1893                         out             => scalar(@out),
1894                         overdue => scalar(@overdue),
1895                         lost            => scalar(@lost),
1896                         claims_returned => scalar(@cr),
1897                         long_overdue            => scalar(@lo)
1898                 };
1899         }
1900
1901         return {
1902                 out             => \@out,
1903                 overdue => \@overdue,
1904                 lost            => \@lost,
1905                 claims_returned => \@cr,
1906                 long_overdue            => \@lo
1907         };
1908 }
1909
1910
1911
1912 __PACKAGE__->register_method(
1913         method          => "checked_in_with_fines",
1914         api_name                => "open-ils.actor.user.checked_in_with_fines",
1915     authoritative => 1,
1916         argc                    => 2,
1917         signature       => q/@see open-ils.actor.user.checked_out/
1918 );
1919 sub checked_in_with_fines {
1920         my( $self, $conn, $auth, $userid ) = @_;
1921
1922         my $e = new_editor(authtoken=>$auth);
1923         return $e->event unless $e->checkauth;
1924
1925         if( $userid ne $e->requestor->id ) {
1926                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1927         }
1928
1929         # money is owed on these items and they are checked in
1930         my $open = $e->search_action_circulation(
1931                 {
1932                         usr                             => $userid, 
1933                         xact_finish             => undef,
1934                         checkin_time    => { "!=" => undef },
1935                 }
1936         );
1937
1938
1939         my( @lost, @cr, @lo );
1940         for my $c (@$open) {
1941                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1942                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1943                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1944         }
1945
1946         return {
1947                 lost            => \@lost,
1948                 claims_returned => \@cr,
1949                 long_overdue            => \@lo
1950         };
1951 }
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961 __PACKAGE__->register_method(
1962         method  => "user_transaction_history",
1963         api_name        => "open-ils.actor.user.transactions.history",
1964         argc            => 1,
1965         notes           => <<"  NOTES");
1966         Returns a list of billable transaction ids for a user, optionally by type
1967         NOTES
1968 __PACKAGE__->register_method(
1969         method  => "user_transaction_history",
1970         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1971         argc            => 1,
1972         notes           => <<"  NOTES");
1973         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1974         NOTES
1975 __PACKAGE__->register_method(
1976         method  => "user_transaction_history",
1977         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1978     authoritative => 1,
1979         argc            => 1,
1980         notes           => <<"  NOTES");
1981         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1982         NOTES
1983 __PACKAGE__->register_method(
1984         method  => "user_transaction_history",
1985         api_name        => "open-ils.actor.user.transactions.history.still_open",
1986         argc            => 1,
1987         notes           => <<"  NOTES");
1988         Returns a list of billable transaction ids for a user that are not finished
1989         NOTES
1990 __PACKAGE__->register_method(
1991         method  => "user_transaction_history",
1992         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1993     authoritative => 1,
1994         argc            => 1,
1995         notes           => <<"  NOTES");
1996         Returns a list of billable transaction ids for a user that has billings
1997         NOTES
1998
1999
2000
2001 =head old
2002 sub _user_transaction_history {
2003         my( $self, $client, $login_session, $user_id, $type ) = @_;
2004
2005         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
2006                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
2007         return $evt if $evt;
2008
2009         my $api = $self->api_name();
2010         my @xact;
2011         my @charge;
2012         my @balance;
2013
2014         @xact = (xact_type =>  $type) if(defined($type));
2015         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
2016         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
2017
2018         $logger->debug("searching for transaction history: @xact : @balance, @charge");
2019
2020         my $trans = $apputils->simple_scalar_request( 
2021                 "open-ils.cstore",
2022                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
2023                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
2024
2025         return [ map { $_->id } @$trans ];
2026 }
2027 =cut
2028
2029 =head SEE APPUTILS.PM
2030 sub _make_mbts {
2031         my @xacts = @_;
2032
2033         my @mbts;
2034         for my $x (@xacts) {
2035                 my $s = new Fieldmapper::money::billable_transaction_summary;
2036                 $s->id( $x->id );
2037                 $s->usr( $x->usr );
2038                 $s->xact_start( $x->xact_start );
2039                 $s->xact_finish( $x->xact_finish );
2040
2041                 my $to = 0;
2042                 my $lb = undef;
2043                 for my $b (@{ $x->billings }) {
2044                         next if ($U->is_true($b->voided));
2045                         $to += ($b->amount * 100);
2046                         $lb ||= $b->billing_ts;
2047                         if ($b->billing_ts ge $lb) {
2048                                 $lb = $b->billing_ts;
2049                                 $s->last_billing_note($b->note);
2050                                 $s->last_billing_ts($b->billing_ts);
2051                                 $s->last_billing_type($b->billing_type);
2052                         }
2053                 }
2054
2055                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2056
2057                 my $tp = 0;
2058                 my $lp = undef;
2059                 for my $p (@{ $x->payments }) {
2060                         next if ($U->is_true($p->voided));
2061                         $tp += ($p->amount * 100);
2062                         $lp ||= $p->payment_ts;
2063                         if ($p->payment_ts ge $lp) {
2064                                 $lp = $p->payment_ts;
2065                                 $s->last_payment_note($p->note);
2066                                 $s->last_payment_ts($p->payment_ts);
2067                                 $s->last_payment_type($p->payment_type);
2068                         }
2069                 }
2070                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2071
2072                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2073
2074                 $s->xact_type( 'grocery' ) if ($x->grocery);
2075                 $s->xact_type( 'circulation' ) if ($x->circulation);
2076
2077                 push @mbts, $s;
2078         }
2079
2080         return @mbts;
2081 }
2082 =cut
2083
2084 sub user_transaction_history {
2085         my( $self, $conn, $auth, $userid, $type ) = @_;
2086
2087         # run inside of a transaction to prevent replication delays
2088         my $e = new_editor(xact=>1, authtoken=>$auth);
2089         return $e->die_event unless $e->checkauth;
2090
2091         if( $e->requestor->id ne $userid ) {
2092                 return $e->die_event 
2093                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2094         }
2095
2096         my $api = $self->api_name;
2097         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2098
2099         my @xacts = @{ $e->search_money_billable_transaction(
2100                 [       { usr => $userid, @xact_finish },
2101                         { flesh => 1,
2102                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2103                           order_by => { mbt => 'xact_start DESC' },
2104                         }
2105                 ],
2106       {substream => 1}
2107         ) };
2108
2109         $e->rollback;
2110
2111         #my @mbts = _make_mbts( @xacts );
2112         my @mbts = $U->make_mbts( @xacts );
2113
2114         if(defined($type)) {
2115                 @mbts = grep { $_->xact_type eq $type } @mbts;
2116         }
2117
2118         if($api =~ /have_balance/o) {
2119                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2120         }
2121
2122         if($api =~ /have_charge/o) {
2123                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2124         }
2125
2126         if($api =~ /have_bill/o) {
2127                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2128         }
2129
2130         return [@mbts];
2131 }
2132
2133
2134
2135 __PACKAGE__->register_method(
2136         method  => "user_perms",
2137         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2138         argc            => 1,
2139         notes           => <<"  NOTES");
2140         Returns a list of permissions
2141         NOTES
2142 sub user_perms {
2143         my( $self, $client, $authtoken, $user ) = @_;
2144
2145         my( $staff, $evt ) = $apputils->checkses($authtoken);
2146         return $evt if $evt;
2147
2148         $user ||= $staff->id;
2149
2150         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2151                 return $evt;
2152         }
2153
2154         return $apputils->simple_scalar_request(
2155                 "open-ils.storage",
2156                 "open-ils.storage.permission.user_perms.atomic",
2157                 $user);
2158 }
2159
2160 __PACKAGE__->register_method(
2161         method  => "retrieve_perms",
2162         api_name        => "open-ils.actor.permissions.retrieve",
2163         notes           => <<"  NOTES");
2164         Returns a list of permissions
2165         NOTES
2166 sub retrieve_perms {
2167         my( $self, $client ) = @_;
2168         return $apputils->simple_scalar_request(
2169                 "open-ils.cstore",
2170                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2171                 { id => { '!=' => undef } }
2172         );
2173 }
2174
2175 __PACKAGE__->register_method(
2176         method  => "retrieve_groups",
2177         api_name        => "open-ils.actor.groups.retrieve",
2178         notes           => <<"  NOTES");
2179         Returns a list of user groupss
2180         NOTES
2181 sub retrieve_groups {
2182         my( $self, $client ) = @_;
2183         return new_editor()->retrieve_all_permission_grp_tree();
2184 }
2185
2186 __PACKAGE__->register_method(
2187         method  => "retrieve_org_address",
2188         api_name        => "open-ils.actor.org_unit.address.retrieve",
2189         notes           => <<'  NOTES');
2190         Returns an org_unit address by ID
2191         @param An org_address ID
2192         NOTES
2193 sub retrieve_org_address {
2194         my( $self, $client, $id ) = @_;
2195         return $apputils->simple_scalar_request(
2196                 "open-ils.cstore",
2197                 "open-ils.cstore.direct.actor.org_address.retrieve",
2198                 $id
2199         );
2200 }
2201
2202 __PACKAGE__->register_method(
2203         method  => "retrieve_groups_tree",
2204         api_name        => "open-ils.actor.groups.tree.retrieve",
2205         notes           => <<"  NOTES");
2206         Returns a list of user groups
2207         NOTES
2208 sub retrieve_groups_tree {
2209         my( $self, $client ) = @_;
2210         return new_editor()->search_permission_grp_tree(
2211                 [
2212                         { parent => undef},
2213                         {       
2214                                 flesh                           => -1,
2215                                 flesh_fields    => { pgt => ["children"] }, 
2216                                 order_by                        => { pgt => 'name'}
2217                         }
2218                 ]
2219         )->[0];
2220 }
2221
2222
2223 # turns an org list into an org tree
2224 =head old code
2225 sub build_group_tree {
2226
2227         my( $self, $grplist) = @_;
2228
2229         return $grplist unless ( 
2230                         ref($grplist) and @$grplist > 1 );
2231
2232         my @list = sort { $a->name cmp $b->name } @$grplist;
2233
2234         my $root;
2235         for my $grp (@list) {
2236
2237                 if ($grp and !defined($grp->parent)) {
2238                         $root = $grp;
2239                         next;
2240                 }
2241                 my ($parent) = grep { $_->id == $grp->parent} @list;
2242
2243                 $parent->children([]) unless defined($parent->children); 
2244                 push( @{$parent->children}, $grp );
2245         }
2246
2247         return $root;
2248 }
2249 =cut
2250
2251
2252 __PACKAGE__->register_method(
2253         method  => "add_user_to_groups",
2254         api_name        => "open-ils.actor.user.set_groups",
2255         notes           => <<"  NOTES");
2256         Adds a user to one or more permission groups
2257         NOTES
2258
2259 sub add_user_to_groups {
2260         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2261
2262         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2263                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2264         return $evt if $evt;
2265
2266         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2267                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2268         return $evt if $evt;
2269
2270         $apputils->simplereq(
2271                 'open-ils.storage',
2272                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2273                 
2274         for my $group (@$groups) {
2275                 my $link = Fieldmapper::permission::usr_grp_map->new;
2276                 $link->grp($group);
2277                 $link->usr($userid);
2278
2279                 my $id = $apputils->simplereq(
2280                         'open-ils.storage',
2281                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2282         }
2283
2284         return 1;
2285 }
2286
2287 __PACKAGE__->register_method(
2288         method  => "get_user_perm_groups",
2289         api_name        => "open-ils.actor.user.get_groups",
2290         notes           => <<"  NOTES");
2291         Retrieve a user's permission groups.
2292         NOTES
2293
2294
2295 sub get_user_perm_groups {
2296         my( $self, $client, $authtoken, $userid ) = @_;
2297
2298         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2299                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2300         return $evt if $evt;
2301
2302         return $apputils->simplereq(
2303                 'open-ils.cstore',
2304                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2305 }       
2306
2307
2308 __PACKAGE__->register_method(
2309         method  => "get_user_work_ous",
2310         api_name        => "open-ils.actor.user.get_work_ous",
2311         notes           => <<"  NOTES");
2312         Retrieve a user's work org units.
2313         NOTES
2314 __PACKAGE__->register_method(
2315         method  => "get_user_work_ous",
2316         api_name        => "open-ils.actor.user.get_work_ous.ids",
2317         notes           => <<"  NOTES");
2318         Retrieve a user's work org units.
2319         NOTES
2320
2321
2322 sub get_user_work_ous {
2323         my( $self, $client, $auth, $userid ) = @_;
2324     my $e = new_editor(authtoken=>$auth);
2325     return $e->event unless $e->checkauth;
2326     $userid ||= $e->requestor->id;
2327
2328     if($e->requestor->id != $userid) {
2329         my $user = $e->retrieve_actor_user($userid)
2330             or return $e->event;
2331         return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2332     }
2333
2334     return $e->search_permission_usr_work_ou_map({usr => $userid})
2335         unless $self->api_name =~ /.ids$/;
2336
2337     # client just wants a list of org IDs
2338     return $U->get_user_work_ou_ids($e, $userid);
2339 }       
2340
2341
2342
2343
2344 __PACKAGE__->register_method (
2345         method          => 'register_workstation',
2346         api_name                => 'open-ils.actor.workstation.register.override',
2347         signature       => q/@see open-ils.actor.workstation.register/);
2348
2349 __PACKAGE__->register_method (
2350         method          => 'register_workstation',
2351         api_name                => 'open-ils.actor.workstation.register',
2352         signature       => q/
2353                 Registers a new workstion in the system
2354                 @param authtoken The login session key
2355                 @param name The name of the workstation id
2356                 @param owner The org unit that owns this workstation
2357                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2358                 if the name is already in use.
2359         /);
2360
2361 sub register_workstation {
2362         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2363
2364         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2365         return $e->die_event unless $e->checkauth;
2366         return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2367         my $existing = $e->search_actor_workstation({name => $name})->[0];
2368
2369         if( $existing ) {
2370
2371                 if( $self->api_name =~ /override/o ) {
2372             # workstation with the given name exists.  
2373
2374             if($owner ne $existing->owning_lib) {
2375                 # if necessary, update the owning_lib of the workstation
2376
2377                 $logger->info("changing owning lib of workstation ".$existing->id.
2378                     " from ".$existing->owning_lib." to $owner");
2379                             return $e->die_event unless 
2380                     $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib); 
2381
2382                             return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner); 
2383
2384                 $existing->owning_lib($owner);
2385                             return $e->die_event unless $e->update_actor_workstation($existing);
2386
2387                 $e->commit;
2388
2389             } else {
2390                 $logger->info(  
2391                     "attempt to register an existing workstation.  returning existing ID");
2392             }
2393
2394             return $existing->id;
2395
2396                 } else {
2397                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2398                 }
2399         }
2400
2401         my $ws = Fieldmapper::actor::workstation->new;
2402         $ws->owning_lib($owner);
2403         $ws->name($name);
2404         $e->create_actor_workstation($ws) or return $e->die_event;
2405         $e->commit;
2406         return $ws->id; # note: editor sets the id on the new object for us
2407 }
2408
2409 __PACKAGE__->register_method (
2410         method          => 'workstation_list',
2411         api_name                => 'open-ils.actor.workstation.list',
2412         signature       => q/
2413                 Returns a list of workstations registered at the given location
2414                 @param authtoken The login session key
2415                 @param ids A list of org_unit.id's for the workstation owners
2416         /);
2417
2418 sub workstation_list {
2419         my( $self, $conn, $authtoken, @orgs ) = @_;
2420
2421         my $e = new_editor(authtoken=>$authtoken);
2422         return $e->event unless $e->checkauth;
2423     my %results;
2424
2425     for my $o (@orgs) {
2426             return $e->event 
2427             unless $e->allowed('REGISTER_WORKSTATION', $o);
2428         $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2429     }
2430     return \%results;
2431 }
2432
2433
2434
2435
2436
2437
2438
2439 __PACKAGE__->register_method (
2440         method          => 'fetch_patron_note',
2441         api_name                => 'open-ils.actor.note.retrieve.all',
2442     authoritative => 1,
2443         signature       => q/
2444                 Returns a list of notes for a given user
2445                 Requestor must have VIEW_USER permission if pub==false and
2446                 @param authtoken The login session key
2447                 @param args Hash of params including
2448                         patronid : the patron's id
2449                         pub : true if retrieving only public notes
2450         /
2451 );
2452
2453 sub fetch_patron_note {
2454         my( $self, $conn, $authtoken, $args ) = @_;
2455         my $patronid = $$args{patronid};
2456
2457         my($reqr, $evt) = $U->checkses($authtoken);
2458         return $evt if $evt;
2459
2460         my $patron;
2461         ($patron, $evt) = $U->fetch_user($patronid);
2462         return $evt if $evt;
2463
2464         if($$args{pub}) {
2465                 if( $patronid ne $reqr->id ) {
2466                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2467                         return $evt if $evt;
2468                 }
2469                 return $U->cstorereq(
2470                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2471                         { usr => $patronid, pub => 't' } );
2472         }
2473
2474         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2475         return $evt if $evt;
2476
2477         return $U->cstorereq(
2478                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2479 }
2480
2481 __PACKAGE__->register_method (
2482         method          => 'create_user_note',
2483         api_name                => 'open-ils.actor.note.create',
2484         signature       => q/
2485                 Creates a new note for the given user
2486                 @param authtoken The login session key
2487                 @param note The note object
2488         /
2489 );
2490 sub create_user_note {
2491         my( $self, $conn, $authtoken, $note ) = @_;
2492         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2493         return $e->die_event unless $e->checkauth;
2494
2495         my $user = $e->retrieve_actor_user($note->usr)
2496                 or return $e->die_event;
2497
2498         return $e->die_event unless 
2499                 $e->allowed('UPDATE_USER',$user->home_ou);
2500
2501         $note->creator($e->requestor->id);
2502         $e->create_actor_usr_note($note) or return $e->die_event;
2503         $e->commit;
2504         return $note->id;
2505 }
2506
2507
2508 __PACKAGE__->register_method (
2509         method          => 'delete_user_note',
2510         api_name                => 'open-ils.actor.note.delete',
2511         signature       => q/
2512                 Deletes a note for the given user
2513                 @param authtoken The login session key
2514                 @param noteid The note id
2515         /
2516 );
2517 sub delete_user_note {
2518         my( $self, $conn, $authtoken, $noteid ) = @_;
2519
2520         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2521         return $e->die_event unless $e->checkauth;
2522         my $note = $e->retrieve_actor_usr_note($noteid)
2523                 or return $e->die_event;
2524         my $user = $e->retrieve_actor_user($note->usr)
2525                 or return $e->die_event;
2526         return $e->die_event unless 
2527                 $e->allowed('UPDATE_USER', $user->home_ou);
2528         
2529         $e->delete_actor_usr_note($note) or return $e->die_event;
2530         $e->commit;
2531         return 1;
2532 }
2533
2534
2535 __PACKAGE__->register_method (
2536         method          => 'update_user_note',
2537         api_name                => 'open-ils.actor.note.update',
2538         signature       => q/
2539                 @param authtoken The login session key
2540                 @param note The note
2541         /
2542 );
2543
2544 sub update_user_note {
2545         my( $self, $conn, $auth, $note ) = @_;
2546         my $e = new_editor(authtoken=>$auth, xact=>1);
2547         return $e->event unless $e->checkauth;
2548         my $patron = $e->retrieve_actor_user($note->usr)
2549                 or return $e->event;
2550         return $e->event unless 
2551                 $e->allowed('UPDATE_USER', $patron->home_ou);
2552         $e->update_actor_user_note($note)
2553                 or return $e->event;
2554         $e->commit;
2555         return 1;
2556 }
2557
2558
2559
2560
2561 __PACKAGE__->register_method (
2562         method          => 'create_closed_date',
2563         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2564         signature       => q/
2565                 Creates a new closing entry for the given org_unit
2566                 @param authtoken The login session key
2567                 @param note The closed_date object
2568         /
2569 );
2570 sub create_closed_date {
2571         my( $self, $conn, $authtoken, $cd ) = @_;
2572
2573         my( $user, $evt ) = $U->checkses($authtoken);
2574         return $evt if $evt;
2575
2576         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2577         return $evt if $evt;
2578
2579         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2580
2581         my $id = $U->storagereq(
2582                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2583         return $U->DB_UPDATE_FAILED($cd) unless $id;
2584         return $id;
2585 }
2586
2587
2588 __PACKAGE__->register_method (
2589         method          => 'delete_closed_date',
2590         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2591         signature       => q/
2592                 Deletes a closing entry for the given org_unit
2593                 @param authtoken The login session key
2594                 @param noteid The close_date id
2595         /
2596 );
2597 sub delete_closed_date {
2598         my( $self, $conn, $authtoken, $cd ) = @_;
2599
2600         my( $user, $evt ) = $U->checkses($authtoken);
2601         return $evt if $evt;
2602
2603         my $cd_obj;
2604         ($cd_obj, $evt) = fetch_closed_date($cd);
2605         return $evt if $evt;
2606
2607         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2608         return $evt if $evt;
2609
2610         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2611
2612         my $stat = $U->storagereq(
2613                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2614         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2615         return $stat;
2616 }
2617
2618
2619 __PACKAGE__->register_method(
2620         method => 'usrname_exists',
2621         api_name        => 'open-ils.actor.username.exists',
2622         signature => q/
2623                 Returns 1 if the requested username exists, returns 0 otherwise
2624         /
2625 );
2626
2627 sub usrname_exists {
2628         my( $self, $conn, $auth, $usrname ) = @_;
2629         my $e = new_editor(authtoken=>$auth);
2630         return $e->event unless $e->checkauth;
2631         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2632         return $$a[0] if $a and @$a;
2633         return undef;
2634 }
2635
2636 __PACKAGE__->register_method(
2637         method => 'barcode_exists',
2638         api_name        => 'open-ils.actor.barcode.exists',
2639     authoritative => 1,
2640         signature => q/
2641                 Returns 1 if the requested barcode exists, returns 0 otherwise
2642         /
2643 );
2644
2645 sub barcode_exists {
2646         my( $self, $conn, $auth, $barcode ) = @_;
2647         my $e = new_editor(authtoken=>$auth);
2648         return $e->event unless $e->checkauth;
2649         my $card = $e->search_actor_card({barcode => $barcode});
2650     return undef unless @$card;
2651     return $card->[0]->usr;
2652 }
2653
2654
2655 __PACKAGE__->register_method(
2656         method => 'retrieve_net_levels',
2657         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2658 );
2659
2660 sub retrieve_net_levels {
2661         my( $self, $conn, $auth ) = @_;
2662         my $e = new_editor(authtoken=>$auth);
2663         return $e->event unless $e->checkauth;
2664         return $e->retrieve_all_config_net_access_level();
2665 }
2666
2667
2668 __PACKAGE__->register_method(
2669         method => 'fetch_org_by_shortname',
2670         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2671 );
2672 sub fetch_org_by_shortname {
2673         my( $self, $conn, $sname ) = @_;
2674         my $e = new_editor();
2675         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2676         return $e->event unless $org;
2677         return $org;
2678 }
2679
2680
2681 __PACKAGE__->register_method(
2682         method => 'session_home_lib',
2683         api_name => 'open-ils.actor.session.home_lib',
2684 );
2685
2686 sub session_home_lib {
2687         my( $self, $conn, $auth ) = @_;
2688         my $e = new_editor(authtoken=>$auth);
2689         return undef unless $e->checkauth;
2690         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2691         return $org->shortname;
2692 }
2693
2694 __PACKAGE__->register_method(
2695         method => 'session_safe_token',
2696         api_name => 'open-ils.actor.session.safe_token',
2697         signature => q/
2698                 Returns a hashed session ID that is safe for export to the world.
2699                 This safe token will expire after 1 hour of non-use.
2700                 @param auth Active authentication token
2701         /
2702 );
2703
2704 sub session_safe_token {
2705         my( $self, $conn, $auth ) = @_;
2706         my $e = new_editor(authtoken=>$auth);
2707         return undef unless $e->checkauth;
2708
2709         my $safe_token = md5_hex($auth);
2710
2711         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2712
2713         # Add more like the following if needed...
2714         $cache->put_cache(
2715                 "safe-token-home_lib-shortname-$safe_token",
2716                 $e->retrieve_actor_org_unit(
2717                         $e->requestor->home_ou
2718                 )->shortname,
2719                 60 * 60
2720         );
2721
2722         return $safe_token;
2723 }
2724
2725
2726 __PACKAGE__->register_method(
2727         method => 'safe_token_home_lib',
2728         api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2729         signature => q/
2730                 Returns the home library shortname from the session
2731                 asscociated with a safe token from generated by
2732                 open-ils.actor.session.safe_token.
2733                 @param safe_token Active safe token
2734         /
2735 );
2736
2737 sub safe_token_home_lib {
2738         my( $self, $conn, $safe_token ) = @_;
2739
2740         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2741         return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2742 }
2743
2744
2745
2746 __PACKAGE__->register_method(
2747         method => 'slim_tree',
2748         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2749 );
2750 sub slim_tree {
2751         my $tree = new_editor()->search_actor_org_unit( 
2752                 [
2753                         {"parent_ou" => undef },
2754                         {
2755                                 flesh                           => -1,
2756                                 flesh_fields    => { aou =>  ['children'] },
2757                                 order_by                        => { aou => 'name'},
2758                                 select                  => { aou => ["id","shortname", "name"]},
2759                         }
2760                 ]
2761         )->[0];
2762
2763         return trim_tree($tree);
2764 }
2765
2766
2767 sub trim_tree {
2768         my $tree = shift;
2769         return undef unless $tree;
2770         my $htree = {
2771                 code => $tree->shortname,
2772                 name => $tree->name,
2773         };
2774         if( $tree->children and @{$tree->children} ) {
2775                 $htree->{children} = [];
2776                 for my $c (@{$tree->children}) {
2777                         push( @{$htree->{children}}, trim_tree($c) );
2778                 }
2779         }
2780
2781         return $htree;
2782 }
2783
2784
2785 __PACKAGE__->register_method(
2786         method  => "update_penalties",
2787         api_name        => "open-ils.actor.user.penalties.update");
2788 sub update_penalties {
2789         my( $self, $conn, $auth, $userid ) = @_;
2790         my $e = new_editor(authtoken=>$auth);
2791         return $e->event unless $e->checkauth;
2792         $U->update_patron_penalties( 
2793                 authtoken => $auth,
2794                 patronid  => $userid,
2795         );
2796         return 1;
2797 }
2798
2799
2800
2801 __PACKAGE__->register_method(
2802         method  => "user_retrieve_fleshed_by_id",
2803         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2804
2805 sub user_retrieve_fleshed_by_id {
2806         my( $self, $client, $auth, $user_id, $fields ) = @_;
2807         my $e = new_editor(authtoken => $auth);
2808         return $e->event unless $e->checkauth;
2809
2810         if( $e->requestor->id != $user_id ) {
2811                 return $e->event unless $e->allowed('VIEW_USER');
2812         }
2813
2814         $fields ||= [
2815                 "cards",
2816                 "card",
2817                 "standing_penalties",
2818                 "addresses",
2819                 "billing_address",
2820                 "mailing_address",
2821                 "stat_cat_entries" ];
2822         return new_flesh_user($user_id, $fields, $e);
2823 }
2824
2825
2826 sub new_flesh_user {
2827
2828         my $id = shift;
2829         my $fields = shift || [];
2830         my $e   = shift || new_editor(xact=>1);
2831
2832         my $user = $e->retrieve_actor_user(
2833         [
2834         $id,
2835         {
2836                 "flesh"                         => 1,
2837                 "flesh_fields" =>  { "au" => $fields }
2838         }
2839         ]
2840         ) or return $e->event;
2841
2842
2843         if( grep { $_ eq 'addresses' } @$fields ) {
2844
2845                 $user->addresses([]) unless @{$user->addresses};
2846         
2847                 if( ref $user->billing_address ) {
2848                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2849                                 push( @{$user->addresses}, $user->billing_address );
2850                         }
2851                 }
2852         
2853                 if( ref $user->mailing_address ) {
2854                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2855                                 push( @{$user->addresses}, $user->mailing_address );
2856                         }
2857                 }
2858         }
2859
2860         $e->rollback;
2861         $user->clear_passwd();
2862         return $user;
2863 }
2864
2865
2866
2867
2868 __PACKAGE__->register_method(
2869         method  => "user_retrieve_parts",
2870         api_name        => "open-ils.actor.user.retrieve.parts",);
2871
2872 sub user_retrieve_parts {
2873         my( $self, $client, $auth, $user_id, $fields ) = @_;
2874         my $e = new_editor(authtoken => $auth);
2875         return $e->event unless $e->checkauth;
2876         if( $e->requestor->id != $user_id ) {
2877                 return $e->event unless $e->allowed('VIEW_USER');
2878         }
2879         my @resp;
2880         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2881         push(@resp, $user->$_()) for(@$fields);
2882         return \@resp;
2883 }
2884
2885
2886
2887 __PACKAGE__->register_method(
2888     method => 'user_opt_in_enabled',
2889     api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2890     signature => q/
2891         @return 1 if user opt-in is globally enabled, 0 otherwise.
2892     /);
2893
2894 sub user_opt_in_enabled {
2895     my($self, $conn) = @_;
2896     my $sc = OpenSRF::Utils::SettingsClient->new;
2897     return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true'; 
2898     return 0;
2899 }
2900     
2901
2902 __PACKAGE__->register_method(
2903     method => 'user_opt_in_at_org',
2904     api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2905     signature => q/
2906         @param $auth The auth token
2907         @param user_id The ID of the user to test
2908         @return 1 if the user has opted in at the specified org,
2909             event on error, and 0 otherwise. /);
2910 sub user_opt_in_at_org {
2911     my($self, $conn, $auth, $user_id) = @_;
2912
2913     # see if we even need to enforce the opt-in value
2914     return 1 unless user_opt_in_enabled($self);
2915
2916         my $e = new_editor(authtoken => $auth);
2917         return $e->event unless $e->checkauth;
2918     my $org_id = $e->requestor->ws_ou;
2919
2920     my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2921         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
2922
2923     # user is automatically opted-in at the home org
2924     return 1 if $user->home_ou eq $org_id;
2925
2926     my $vals = $e->search_actor_usr_org_unit_opt_in(
2927         {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
2928
2929     return 1 if @$vals;
2930     return 0;
2931 }
2932
2933 __PACKAGE__->register_method(
2934     method => 'create_user_opt_in_at_org',
2935     api_name => 'open-ils.actor.user.org_unit_opt_in.create',
2936     signature => q/
2937         @param $auth The auth token
2938         @param user_id The ID of the user to test
2939         @return The ID of the newly created object, event on error./);
2940
2941 sub create_user_opt_in_at_org {
2942     my($self, $conn, $auth, $user_id) = @_;
2943
2944         my $e = new_editor(authtoken => $auth, xact=>1);
2945         return $e->die_event unless $e->checkauth;
2946     my $org_id = $e->requestor->ws_ou;
2947
2948     my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
2949         return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
2950
2951     my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
2952
2953     $opt_in->org_unit($org_id);
2954     $opt_in->usr($user_id);
2955     $opt_in->staff($e->requestor->id);
2956     $opt_in->opt_in_ts('now');
2957     $opt_in->opt_in_ws($e->requestor->wsid);
2958
2959     $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
2960         or return $e->die_event;
2961
2962     $e->commit;
2963
2964     return $opt_in->id;
2965 }
2966
2967
2968 1;
2969