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