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