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