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