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