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