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