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