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