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