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