]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
Fix typo: open-ils.actor.retrieve_by_shorname (but keep the old one around just in...
[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 => 'Get the org unit setting value associated with the setting name as seen from the specified org unit.  ' .
272                 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given '         .
273                 'user has permission to view that setting, if there is a permission associated with the setting.'        ,
274         params => [
275             { desc => 'org unit id',          type => 'number' },
276             { desc => 'setting name',         type => 'string' },
277             { desc => 'authtoken (optional)', type => 'string' }
278         ],
279         return => {desc => 'A value for the org unit setting, or undef'}
280     }
281 );
282
283 # ------------------------------------------------------------------
284 # Attempts to find the org setting value for a given org.  if not 
285 # found at the requested org, searches up the org tree until it 
286 # finds a parent that has the requested setting.
287 # when found, returns { org => $id, value => $value }
288 # otherwise, returns NULL
289 # ------------------------------------------------------------------
290 sub ou_ancestor_setting {
291     my( $self, $client, $orgid, $name, $auth ) = @_;
292     return $U->ou_ancestor_setting($orgid, $name, undef, $auth);
293 }
294
295 __PACKAGE__->register_method(
296     api_name  => 'open-ils.actor.ou_setting.ancestor_default.batch',
297     method    => 'ou_ancestor_setting_batch',
298     signature => {
299         desc => 'Get org unit setting name => value pairs for a list of names, as seen from the specified org unit.  ' .
300                 'IF AND ONLY IF an authentication token is provided, this method will make sure that the given '       .
301                 'user has permission to view that setting, if there is a permission associated with the setting.'      ,
302         params => [
303             { desc => 'org unit id',          type => 'number' },
304             { desc => 'setting name list',    type => 'array'  },
305             { desc => 'authtoken (optional)', type => 'string' }
306         ],
307         return => {desc => 'A hash with name => value pairs for the org unit settings'}
308     }
309 );
310 sub ou_ancestor_setting_batch {
311     my( $self, $client, $orgid, $name_list, $auth ) = @_;
312     my %values;
313     $values{$_} = $U->ou_ancestor_setting($orgid, $_, undef, $auth) for @$name_list;
314     return \%values;
315 }
316
317
318
319 __PACKAGE__->register_method(
320         method  => "update_patron",
321         api_name        => "open-ils.actor.patron.update",);
322
323 sub update_patron {
324         my( $self, $client, $user_session, $patron ) = @_;
325
326         my $session = $apputils->start_db_session();
327         my $err = undef;
328
329         $logger->info("Creating new patron...") if $patron->isnew; 
330         $logger->info("Updating Patron: " . $patron->id) unless $patron->isnew;
331
332         my( $user_obj, $evt ) = $U->checkses($user_session);
333         return $evt if $evt;
334
335         $evt = check_group_perm($session, $user_obj, $patron);
336         return $evt if $evt;
337
338
339         # $new_patron is the patron in progress.  $patron is the original patron
340         # passed in with the method.  new_patron will change as the components
341         # of patron are added/updated.
342
343         my $new_patron;
344
345         # unflesh the real items on the patron
346         $patron->card( $patron->card->id ) if(ref($patron->card));
347         $patron->billing_address( $patron->billing_address->id ) 
348                 if(ref($patron->billing_address));
349         $patron->mailing_address( $patron->mailing_address->id ) 
350                 if(ref($patron->mailing_address));
351
352         # create/update the patron first so we can use his id
353         if($patron->isnew()) {
354                 ( $new_patron, $evt ) = _add_patron($session, _clone_patron($patron), $user_obj);
355                 return $evt if $evt;
356         } else { $new_patron = $patron; }
357
358         ( $new_patron, $evt ) = _add_update_addresses($session, $patron, $new_patron, $user_obj);
359         return $evt if $evt;
360
361         ( $new_patron, $evt ) = _add_update_cards($session, $patron, $new_patron, $user_obj);
362         return $evt if $evt;
363
364         ( $new_patron, $evt ) = _add_survey_responses($session, $patron, $new_patron, $user_obj);
365         return $evt if $evt;
366
367         # re-update the patron if anything has happened to him during this process
368         if($new_patron->ischanged()) {
369                 ( $new_patron, $evt ) = _update_patron($session, $new_patron, $user_obj);
370                 return $evt if $evt;
371         }
372
373         ($new_patron, $evt) = _create_stat_maps($session, $user_session, $patron, $new_patron, $user_obj);
374         return $evt if $evt;
375
376         ($new_patron, $evt) = _create_perm_maps($session, $user_session, $patron, $new_patron, $user_obj);
377         return $evt if $evt;
378
379         $apputils->commit_db_session($session);
380
381     $evt = apply_invalid_addr_penalty($patron);
382     return $evt if $evt;
383
384     my $tses = OpenSRF::AppSession->create('open-ils.trigger');
385         if($patron->isnew) {
386         $tses->request('open-ils.trigger.event.autocreate', 'au.create', $new_patron, $new_patron->home_ou);
387         } else {
388         $tses->request('open-ils.trigger.event.autocreate', 'au.update', $new_patron, $new_patron->home_ou);
389     }
390
391         return flesh_user($new_patron->id(), new_editor(requestor => $user_obj, xact => 1));
392 }
393
394 sub apply_invalid_addr_penalty {
395     my $patron = shift;
396     my $e = new_editor(xact => 1);
397
398     # grab the invalid address penalty if set
399     my $penalties = OpenILS::Utils::Penalty->retrieve_usr_penalties($e, $patron->id, $patron->home_ou);
400
401     my ($addr_penalty) = grep 
402         { $_->standing_penalty->name eq 'INVALID_PATRON_ADDRESS' } @$penalties;
403     
404     # do we enforce invalid address penalty
405     my $enforce = $U->ou_ancestor_setting_value(
406         $patron->home_ou, 'circ.patron_invalid_address_apply_penalty') || 0;
407
408     my $addrs = $e->search_actor_user_address(
409         {usr => $patron->id, valid => 'f', id => {'>' => 0}}, {idlist => 1});
410     my $addr_count = scalar(@$addrs);
411
412     if($addr_count == 0 and $addr_penalty) {
413
414         # regardless of any settings, remove the penalty when the user has no invalid addresses
415         $e->delete_actor_user_standing_penalty($addr_penalty) or return $e->die_event;
416         $e->commit;
417
418     } elsif($enforce and $addr_count > 0 and !$addr_penalty) {
419         
420         my $ptype = $e->retrieve_config_standing_penalty(29) or return $e->die_event;
421         my $depth = $ptype->org_depth;
422         my $ctx_org = $U->org_unit_ancestor_at_depth($patron->home_ou, $depth) if defined $depth;
423         $ctx_org = $patron->home_ou unless defined $ctx_org;
424         
425         my $penalty = Fieldmapper::actor::user_standing_penalty->new;
426         $penalty->usr($patron->id);
427         $penalty->org_unit($ctx_org);
428         $penalty->standing_penalty(OILS_PENALTY_INVALID_PATRON_ADDRESS);
429
430         $e->create_actor_user_standing_penalty($penalty) or return $e->die_event;
431         $e->commit;
432
433     } else {
434         $e->rollback;
435     }
436
437     return undef;
438 }
439
440
441 sub flesh_user {
442         my $id = shift;
443     my $e = shift;
444         return new_flesh_user($id, [
445                 "cards",
446                 "card",
447                 "standing_penalties",
448                 "addresses",
449                 "billing_address",
450                 "mailing_address",
451                 "stat_cat_entries" ], $e );
452 }
453
454
455
456
457
458
459 # clone and clear stuff that would break the database
460 sub _clone_patron {
461         my $patron = shift;
462
463         my $new_patron = $patron->clone;
464         # clear these
465         $new_patron->clear_billing_address();
466         $new_patron->clear_mailing_address();
467         $new_patron->clear_addresses();
468         $new_patron->clear_card();
469         $new_patron->clear_cards();
470         $new_patron->clear_id();
471         $new_patron->clear_isnew();
472         $new_patron->clear_ischanged();
473         $new_patron->clear_isdeleted();
474         $new_patron->clear_stat_cat_entries();
475         $new_patron->clear_permissions();
476         $new_patron->clear_standing_penalties();
477
478         return $new_patron;
479 }
480
481
482 sub _add_patron {
483
484         my $session             = shift;
485         my $patron              = shift;
486         my $user_obj    = shift;
487
488         my $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'CREATE_USER');
489         return (undef, $evt) if $evt;
490
491         my $ex = $session->request(
492                 'open-ils.storage.direct.actor.user.search.usrname', $patron->usrname())->gather(1);
493         if( $ex and @$ex ) {
494                 return (undef, OpenILS::Event->new('USERNAME_EXISTS'));
495         }
496
497         $logger->info("Creating new user in the DB with username: ".$patron->usrname());
498
499         my $id = $session->request(
500                 "open-ils.storage.direct.actor.user.create", $patron)->gather(1);
501         return (undef, $U->DB_UPDATE_FAILED($patron)) unless $id;
502
503         $logger->info("Successfully created new user [$id] in DB");
504
505         return ( $session->request( 
506                 "open-ils.storage.direct.actor.user.retrieve", $id)->gather(1), undef );
507 }
508
509
510 sub check_group_perm {
511         my( $session, $requestor, $patron ) = @_;
512         my $evt;
513
514         # first let's see if the requestor has 
515         # priveleges to update this user in any way
516         if( ! $patron->isnew ) {
517                 my $p = $session->request(
518                         'open-ils.storage.direct.actor.user.retrieve', $patron->id )->gather(1);
519
520                 # If we are the requestor (trying to update our own account)
521                 # and we are not trying to change our profile, we're good
522                 if( $p->id == $requestor->id and 
523                                 $p->profile == $patron->profile ) {
524                         return undef;
525                 }
526
527
528                 $evt = group_perm_failed($session, $requestor, $p);
529                 return $evt if $evt;
530         }
531
532         # They are allowed to edit this patron.. can they put the 
533         # patron into the group requested?
534         $evt = group_perm_failed($session, $requestor, $patron);
535         return $evt if $evt;
536         return undef;
537 }
538
539
540 sub group_perm_failed {
541         my( $session, $requestor, $patron ) = @_;
542
543         my $perm;
544         my $grp;
545         my $grpid = $patron->profile;
546
547         do {
548
549                 $logger->debug("user update looking for group perm for group $grpid");
550                 $grp = $session->request(
551                         'open-ils.storage.direct.permission.grp_tree.retrieve', $grpid )->gather(1);
552                 return OpenILS::Event->new('PERMISSION_GRP_TREE_NOT_FOUND') unless $grp;
553
554         } while( !($perm = $grp->application_perm) and ($grpid = $grp->parent) );
555
556         $logger->info("user update checking perm $perm on user ".
557                 $requestor->id." for update/create on user username=".$patron->usrname);
558
559         my $evt = $U->check_perms($requestor->id, $patron->home_ou, $perm);
560         return $evt if $evt;
561         return undef;
562 }
563
564
565
566 sub _update_patron {
567         my( $session, $patron, $user_obj, $noperm) = @_;
568
569         $logger->info("Updating patron ".$patron->id." in DB");
570
571         my $evt;
572
573         if(!$noperm) {
574                 $evt = $U->check_perms($user_obj->id, $patron->home_ou, 'UPDATE_USER');
575                 return (undef, $evt) if $evt;
576         }
577
578         # update the password by itself to avoid the password protection magic
579         if( $patron->passwd ) {
580                 my $s = $session->request(
581                         'open-ils.storage.direct.actor.user.remote_update',
582                         {id => $patron->id}, {passwd => $patron->passwd})->gather(1);
583                 return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($s);
584                 $patron->clear_passwd;
585         }
586
587         if(!$patron->ident_type) {
588                 $patron->clear_ident_type;
589                 $patron->clear_ident_value;
590         }
591
592     $evt = verify_last_xact($session, $patron);
593     return (undef, $evt) if $evt;
594
595         my $stat = $session->request(
596                 "open-ils.storage.direct.actor.user.update",$patron )->gather(1);
597         return (undef, $U->DB_UPDATE_FAILED($patron)) unless defined($stat);
598
599         return ($patron);
600 }
601
602 sub verify_last_xact {
603     my( $session, $patron ) = @_;
604     return undef unless $patron->id and $patron->id > 0;
605     my $p = $session->request(
606         'open-ils.storage.direct.actor.user.retrieve', $patron->id)->gather(1);
607     my $xact = $p->last_xact_id;
608     return undef unless $xact;
609     $logger->info("user xact = $xact, saving with xact " . $patron->last_xact_id);
610     return OpenILS::Event->new('XACT_COLLISION')
611         if $xact != $patron->last_xact_id;
612     return undef;
613 }
614
615
616 sub _check_dup_ident {
617         my( $session, $patron ) = @_;
618
619         return undef unless $patron->ident_value;
620
621         my $search = {
622                 ident_type      => $patron->ident_type, 
623                 ident_value => $patron->ident_value,
624         };
625
626         $logger->debug("patron update searching for dup ident values: " . 
627                 $patron->ident_type . ':' . $patron->ident_value);
628
629         $search->{id} = {'!=' => $patron->id} if $patron->id and $patron->id > 0;
630
631         my $dups = $session->request(
632                 'open-ils.storage.direct.actor.user.search_where.atomic', $search )->gather(1);
633
634
635         return OpenILS::Event->new('PATRON_DUP_IDENT1', payload => $patron )
636                 if $dups and @$dups;
637
638         return undef;
639 }
640
641
642 sub _add_update_addresses {
643
644         my $session = shift;
645         my $patron = shift;
646         my $new_patron = shift;
647
648         my $evt;
649
650         my $current_id; # id of the address before creation
651
652         for my $address (@{$patron->addresses()}) {
653
654                 next unless ref $address;
655                 $current_id = $address->id();
656
657                 if( $patron->billing_address() and
658                         $patron->billing_address() == $current_id ) {
659                         $logger->info("setting billing addr to $current_id");
660                         $new_patron->billing_address($address->id());
661                         $new_patron->ischanged(1);
662                 }
663         
664                 if( $patron->mailing_address() and
665                         $patron->mailing_address() == $current_id ) {
666                         $new_patron->mailing_address($address->id());
667                         $logger->info("setting mailing addr to $current_id");
668                         $new_patron->ischanged(1);
669                 }
670
671
672                 if($address->isnew()) {
673
674                         $address->usr($new_patron->id());
675
676                         ($address, $evt) = _add_address($session,$address);
677                         return (undef, $evt) if $evt;
678
679                         # we need to get the new id
680                         if( $patron->billing_address() and 
681                                         $patron->billing_address() == $current_id ) {
682                                 $new_patron->billing_address($address->id());
683                                 $logger->info("setting billing addr to $current_id");
684                                 $new_patron->ischanged(1);
685                         }
686
687                         if( $patron->mailing_address() and
688                                         $patron->mailing_address() == $current_id ) {
689                                 $new_patron->mailing_address($address->id());
690                                 $logger->info("setting mailing addr to $current_id");
691                                 $new_patron->ischanged(1);
692                         }
693
694                 } elsif($address->ischanged() ) {
695
696                         ($address, $evt) = _update_address($session, $address);
697                         return (undef, $evt) if $evt;
698
699                 } elsif($address->isdeleted() ) {
700
701                         if( $address->id() == $new_patron->mailing_address() ) {
702                                 $new_patron->clear_mailing_address();
703                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
704                                 return (undef, $evt) if $evt;
705                         }
706
707                         if( $address->id() == $new_patron->billing_address() ) {
708                                 $new_patron->clear_billing_address();
709                                 ($new_patron, $evt) = _update_patron($session, $new_patron);
710                                 return (undef, $evt) if $evt;
711                         }
712
713                         $evt = _delete_address($session, $address);
714                         return (undef, $evt) if $evt;
715                 } 
716         }
717
718         return ( $new_patron, undef );
719 }
720
721
722 # adds an address to the db and returns the address with new id
723 sub _add_address {
724         my($session, $address) = @_;
725         $address->clear_id();
726
727         $logger->info("Creating new address at street ".$address->street1);
728
729         # put the address into the database
730         my $id = $session->request(
731                 "open-ils.storage.direct.actor.user_address.create", $address )->gather(1);
732         return (undef, $U->DB_UPDATE_FAILED($address)) unless $id;
733
734         $address->id( $id );
735         return ($address, undef);
736 }
737
738
739 sub _update_address {
740         my( $session, $address ) = @_;
741
742         $logger->info("Updating address ".$address->id." in the DB");
743
744         my $stat = $session->request(
745                 "open-ils.storage.direct.actor.user_address.update", $address )->gather(1);
746
747         return (undef, $U->DB_UPDATE_FAILED($address)) unless defined($stat);
748         return ($address, undef);
749 }
750
751
752
753 sub _add_update_cards {
754
755         my $session = shift;
756         my $patron = shift;
757         my $new_patron = shift;
758
759         my $evt;
760
761         my $virtual_id; #id of the card before creation
762         for my $card (@{$patron->cards()}) {
763
764                 $card->usr($new_patron->id());
765
766                 if(ref($card) and $card->isnew()) {
767
768                         $virtual_id = $card->id();
769                         ( $card, $evt ) = _add_card($session,$card);
770                         return (undef, $evt) if $evt;
771
772                         #if(ref($patron->card)) { $patron->card($patron->card->id); }
773                         if($patron->card() == $virtual_id) {
774                                 $new_patron->card($card->id());
775                                 $new_patron->ischanged(1);
776                         }
777
778                 } elsif( ref($card) and $card->ischanged() ) {
779                         $evt = _update_card($session, $card);
780                         return (undef, $evt) if $evt;
781                 }
782         }
783
784         return ( $new_patron, undef );
785 }
786
787
788 # adds an card to the db and returns the card with new id
789 sub _add_card {
790         my( $session, $card ) = @_;
791         $card->clear_id();
792
793         $logger->info("Adding new patron card ".$card->barcode);
794
795         my $id = $session->request(
796                 "open-ils.storage.direct.actor.card.create", $card )->gather(1);
797         return (undef, $U->DB_UPDATE_FAILED($card)) unless $id;
798         $logger->info("Successfully created patron card $id");
799
800         $card->id($id);
801         return ( $card, undef );
802 }
803
804
805 # returns event on error.  returns undef otherwise
806 sub _update_card {
807         my( $session, $card ) = @_;
808         $logger->info("Updating patron card ".$card->id);
809
810         my $stat = $session->request(
811                 "open-ils.storage.direct.actor.card.update", $card )->gather(1);
812         return $U->DB_UPDATE_FAILED($card) unless defined($stat);
813         return undef;
814 }
815
816
817
818
819 # returns event on error.  returns undef otherwise
820 sub _delete_address {
821         my( $session, $address ) = @_;
822
823         $logger->info("Deleting address ".$address->id." from DB");
824
825         my $stat = $session->request(
826                 "open-ils.storage.direct.actor.user_address.delete", $address )->gather(1);
827
828         return $U->DB_UPDATE_FAILED($address) unless defined($stat);
829         return undef;
830 }
831
832
833
834 sub _add_survey_responses {
835         my ($session, $patron, $new_patron) = @_;
836
837         $logger->info( "Updating survey responses for patron ".$new_patron->id );
838
839         my $responses = $patron->survey_responses;
840
841         if($responses) {
842
843                 $_->usr($new_patron->id) for (@$responses);
844
845                 my $evt = $U->simplereq( "open-ils.circ", 
846                         "open-ils.circ.survey.submit.user_id", $responses );
847
848                 return (undef, $evt) if defined($U->event_code($evt));
849
850         }
851
852         return ( $new_patron, undef );
853 }
854
855
856 sub _create_stat_maps {
857
858         my($session, $user_session, $patron, $new_patron) = @_;
859
860         my $maps = $patron->stat_cat_entries();
861
862         for my $map (@$maps) {
863
864                 my $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.update";
865
866                 if ($map->isdeleted()) {
867                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.delete";
868
869                 } elsif ($map->isnew()) {
870                         $method = "open-ils.storage.direct.actor.stat_cat_entry_user_map.create";
871                         $map->clear_id;
872                 }
873
874
875                 $map->target_usr($new_patron->id);
876
877                 #warn "
878                 $logger->info("Updating stat entry with method $method and map $map");
879
880                 my $stat = $session->request($method, $map)->gather(1);
881                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
882
883         }
884
885         return ($new_patron, undef);
886 }
887
888 sub _create_perm_maps {
889
890         my($session, $user_session, $patron, $new_patron) = @_;
891
892         my $maps = $patron->permissions;
893
894         for my $map (@$maps) {
895
896                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
897                 if ($map->isdeleted()) {
898                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
899                 } elsif ($map->isnew()) {
900                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
901                         $map->clear_id;
902                 }
903
904
905                 $map->usr($new_patron->id);
906
907                 #warn( "Updating permissions with method $method and session $user_session and map $map" );
908                 $logger->info( "Updating permissions with method $method and map $map" );
909
910                 my $stat = $session->request($method, $map)->gather(1);
911                 return (undef, $U->DB_UPDATE_FAILED($map)) unless defined($stat);
912
913         }
914
915         return ($new_patron, undef);
916 }
917
918
919 __PACKAGE__->register_method(
920         method  => "set_user_work_ous",
921         api_name        => "open-ils.actor.user.work_ous.update",
922 );
923
924 sub set_user_work_ous {
925         my $self = shift;
926         my $client = shift;
927         my $ses = shift;
928         my $maps = shift;
929
930         my( $requestor, $evt ) = $apputils->checksesperm( $ses, 'ASSIGN_WORK_ORG_UNIT' );
931         return $evt if $evt;
932
933         my $session = $apputils->start_db_session();
934
935         for my $map (@$maps) {
936
937                 my $method = "open-ils.storage.direct.permission.usr_work_ou_map.update";
938                 if ($map->isdeleted()) {
939                         $method = "open-ils.storage.direct.permission.usr_work_ou_map.delete";
940                 } elsif ($map->isnew()) {
941                         $method = "open-ils.storage.direct.permission.usr_work_ou_map.create";
942                         $map->clear_id;
943                 }
944
945                 #warn( "Updating permissions with method $method and session $ses and map $map" );
946                 $logger->info( "Updating work_ou map with method $method and map $map" );
947
948                 my $stat = $session->request($method, $map)->gather(1);
949                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
950
951         }
952
953         $apputils->commit_db_session($session);
954
955         return scalar(@$maps);
956 }
957
958
959 __PACKAGE__->register_method(
960         method  => "set_user_perms",
961         api_name        => "open-ils.actor.user.permissions.update",
962 );
963
964 sub set_user_perms {
965         my $self = shift;
966         my $client = shift;
967         my $ses = shift;
968         my $maps = shift;
969
970         my $session = $apputils->start_db_session();
971
972         my( $user_obj, $evt ) = $U->checkses($ses);
973         return $evt if $evt;
974
975         my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
976
977         my $all = undef;
978         $all = 1 if ($U->is_true($user_obj->super_user()));
979     $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
980
981         for my $map (@$maps) {
982
983                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
984                 if ($map->isdeleted()) {
985                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
986                 } elsif ($map->isnew()) {
987                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
988                         $map->clear_id;
989                 }
990
991                 next if (!$all and !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
992                 #warn( "Updating permissions with method $method and session $ses and map $map" );
993                 $logger->info( "Updating permissions with method $method and map $map" );
994
995                 my $stat = $session->request($method, $map)->gather(1);
996                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
997
998         }
999
1000         $apputils->commit_db_session($session);
1001
1002         return scalar(@$maps);
1003 }
1004
1005
1006 __PACKAGE__->register_method(
1007         method  => "user_retrieve_by_barcode",
1008     authoritative => 1,
1009         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
1010
1011 sub user_retrieve_by_barcode {
1012         my($self, $client, $user_session, $barcode) = @_;
1013
1014         $logger->debug("Searching for user with barcode $barcode");
1015         my ($user_obj, $evt) = $apputils->checkses($user_session);
1016         return $evt if $evt;
1017
1018         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
1019                         "open-ils.cstore", 
1020                         "open-ils.cstore.direct.actor.card.search.atomic",
1021                         { barcode => $barcode }
1022         );
1023
1024         if(!$card || !$card->[0]) {
1025                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
1026         }
1027
1028         $card = $card->[0];
1029         my $user = flesh_user($card->usr(), new_editor(requestor => $user_obj));
1030
1031         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
1032         return $evt if $evt;
1033
1034         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
1035         return $user;
1036
1037 }
1038
1039
1040
1041 __PACKAGE__->register_method(
1042         method  => "get_user_by_id",
1043     authoritative => 1,
1044         api_name        => "open-ils.actor.user.retrieve",);
1045
1046 sub get_user_by_id {
1047         my ($self, $client, $auth, $id) = @_;
1048         my $e = new_editor(authtoken=>$auth);
1049         return $e->event unless $e->checkauth;
1050         my $user = $e->retrieve_actor_user($id)
1051                 or return $e->event;
1052         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
1053         return $user;
1054 }
1055
1056
1057
1058 __PACKAGE__->register_method(
1059         method  => "get_org_types",
1060         api_name        => "open-ils.actor.org_types.retrieve",);
1061
1062 sub get_org_types {
1063     return $U->get_org_types();
1064 }
1065
1066
1067
1068 __PACKAGE__->register_method(
1069         method  => "get_user_ident_types",
1070         api_name        => "open-ils.actor.user.ident_types.retrieve",
1071 );
1072 my $ident_types;
1073 sub get_user_ident_types {
1074         return $ident_types if $ident_types;
1075         return $ident_types = 
1076                 new_editor()->retrieve_all_config_identification_type();
1077 }
1078
1079
1080
1081
1082 __PACKAGE__->register_method(
1083         method  => "get_org_unit",
1084         api_name        => "open-ils.actor.org_unit.retrieve",
1085 );
1086
1087 sub get_org_unit {
1088         my( $self, $client, $user_session, $org_id ) = @_;
1089         my $e = new_editor(authtoken => $user_session);
1090         if(!$org_id) {
1091                 return $e->event unless $e->checkauth;
1092                 $org_id = $e->requestor->ws_ou;
1093         }
1094         my $o = $e->retrieve_actor_org_unit($org_id)
1095                 or return $e->event;
1096         return $o;
1097 }
1098
1099 __PACKAGE__->register_method(
1100         method  => "search_org_unit",
1101         api_name        => "open-ils.actor.org_unit_list.search",
1102 );
1103
1104 sub search_org_unit {
1105
1106         my( $self, $client, $field, $value ) = @_;
1107
1108         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1109                 "open-ils.cstore",
1110                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
1111                 { $field => $value } );
1112
1113         return $list;
1114 }
1115
1116
1117 # build the org tree
1118
1119 __PACKAGE__->register_method(
1120         method  => "get_org_tree",
1121         api_name        => "open-ils.actor.org_tree.retrieve",
1122         argc            => 0, 
1123         note            => "Returns the entire org tree structure",
1124 );
1125
1126 sub get_org_tree {
1127         my $self = shift;
1128         my $client = shift;
1129         return $U->get_org_tree($client->session->session_locale);
1130 }
1131
1132
1133 __PACKAGE__->register_method(
1134         method  => "get_org_descendants",
1135         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1136 );
1137
1138 # depth is optional.  org_unit is the id
1139 sub get_org_descendants {
1140         my( $self, $client, $org_unit, $depth ) = @_;
1141
1142     if(ref $org_unit eq 'ARRAY') {
1143         $depth ||= [];
1144         my @trees;
1145         for my $i (0..scalar(@$org_unit)-1) {
1146             my $list = $U->simple_scalar_request(
1147                             "open-ils.storage", 
1148                             "open-ils.storage.actor.org_unit.descendants.atomic",
1149                             $org_unit->[$i], $depth->[$i] );
1150             push(@trees, $U->build_org_tree($list));
1151         }
1152         return \@trees;
1153
1154     } else {
1155             my $orglist = $apputils->simple_scalar_request(
1156                             "open-ils.storage", 
1157                             "open-ils.storage.actor.org_unit.descendants.atomic",
1158                             $org_unit, $depth );
1159             return $U->build_org_tree($orglist);
1160     }
1161 }
1162
1163
1164 __PACKAGE__->register_method(
1165         method  => "get_org_ancestors",
1166         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1167 );
1168
1169 # depth is optional.  org_unit is the id
1170 sub get_org_ancestors {
1171         my( $self, $client, $org_unit, $depth ) = @_;
1172         my $orglist = $apputils->simple_scalar_request(
1173                         "open-ils.storage", 
1174                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1175                         $org_unit, $depth );
1176         return $U->build_org_tree($orglist);
1177 }
1178
1179
1180 __PACKAGE__->register_method(
1181         method  => "get_standings",
1182         api_name        => "open-ils.actor.standings.retrieve"
1183 );
1184
1185 my $user_standings;
1186 sub get_standings {
1187         return $user_standings if $user_standings;
1188         return $user_standings = 
1189                 $apputils->simple_scalar_request(
1190                         "open-ils.cstore",
1191                         "open-ils.cstore.direct.config.standing.search.atomic",
1192                         { id => { "!=" => undef } }
1193                 );
1194 }
1195
1196
1197
1198 __PACKAGE__->register_method(
1199         method  => "get_my_org_path",
1200         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1201 );
1202
1203 sub get_my_org_path {
1204         my( $self, $client, $auth, $org_id ) = @_;
1205         my $e = new_editor(authtoken=>$auth);
1206         return $e->event unless $e->checkauth;
1207         $org_id = $e->requestor->ws_ou unless defined $org_id;
1208
1209         return $apputils->simple_scalar_request(
1210                 "open-ils.storage",
1211                 "open-ils.storage.actor.org_unit.full_path.atomic",
1212                 $org_id );
1213 }
1214
1215
1216 __PACKAGE__->register_method(
1217         method  => "patron_adv_search",
1218         api_name        => "open-ils.actor.patron.search.advanced" );
1219 sub patron_adv_search {
1220         my( $self, $client, $auth, $search_hash, 
1221         $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1222
1223         my $e = new_editor(authtoken=>$auth);
1224         return $e->event unless $e->checkauth;
1225         return $e->event unless $e->allowed('VIEW_USER');
1226         return $U->storagereq(
1227                 "open-ils.storage.actor.user.crazy_search", $search_hash, 
1228             $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1229 }
1230
1231
1232 __PACKAGE__->register_method(
1233         method  => "update_passwd",
1234     authoritative => 1,
1235         api_name        => "open-ils.actor.user.password.update");
1236
1237 __PACKAGE__->register_method(
1238         method  => "update_passwd",
1239         api_name        => "open-ils.actor.user.username.update");
1240
1241 __PACKAGE__->register_method(
1242         method  => "update_passwd",
1243         api_name        => "open-ils.actor.user.email.update");
1244
1245 sub update_passwd {
1246     my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1247     my $e = new_editor(xact=>1, authtoken=>$auth);
1248     return $e->die_event unless $e->checkauth;
1249
1250     my $db_user = $e->retrieve_actor_user($e->requestor->id)
1251         or return $e->die_event;
1252     my $api = $self->api_name;
1253
1254     if( $api =~ /password/o ) {
1255
1256         # make sure the original password matches the in-database password
1257         return OpenILS::Event->new('INCORRECT_PASSWORD')
1258             if md5_hex($orig_pw) ne $db_user->passwd;
1259         $db_user->passwd($new_val);
1260
1261     } else {
1262
1263         # if we don't clear the password, the user will be updated with
1264         # a hashed version of the hashed version of their password
1265         $db_user->clear_passwd;
1266
1267         if( $api =~ /username/o ) {
1268
1269             # make sure no one else has this username
1270             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
1271                         return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1272             $db_user->usrname($new_val);
1273
1274         } elsif( $api =~ /email/o ) {
1275             $db_user->email($new_val);
1276         }
1277     }
1278
1279     $e->update_actor_user($db_user) or return $e->die_event;
1280     $e->commit;
1281     return 1;
1282 }
1283
1284
1285
1286
1287 __PACKAGE__->register_method(
1288         method  => "check_user_perms",
1289         api_name        => "open-ils.actor.user.perm.check",
1290         notes           => <<"  NOTES");
1291         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1292         perm type, if the user does *not* have the given permission it is added
1293         to a list which is returned from the method.  If all permissions
1294         are allowed, an empty list is returned
1295         if the logged in user does not match 'user_id', then the logged in user must
1296         have VIEW_PERMISSION priveleges.
1297         NOTES
1298
1299 sub check_user_perms {
1300         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1301
1302         my( $staff, $evt ) = $apputils->checkses($login_session);
1303         return $evt if $evt;
1304
1305         if($staff->id ne $user_id) {
1306                 if( $evt = $apputils->check_perms(
1307                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1308                         return $evt;
1309                 }
1310         }
1311
1312         my @not_allowed;
1313         for my $perm (@$perm_types) {
1314                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1315                         push @not_allowed, $perm;
1316                 }
1317         }
1318
1319         return \@not_allowed
1320 }
1321
1322 __PACKAGE__->register_method(
1323         method  => "check_user_perms2",
1324         api_name        => "open-ils.actor.user.perm.check.multi_org",
1325         notes           => q/
1326                 Checks the permissions on a list of perms and orgs for a user
1327                 @param authtoken The login session key
1328                 @param user_id The id of the user to check
1329                 @param orgs The array of org ids
1330                 @param perms The array of permission names
1331                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1332                 if the logged in user does not match 'user_id', then the logged in user must
1333                 have VIEW_PERMISSION priveleges.
1334         /);
1335
1336 sub check_user_perms2 {
1337         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1338
1339         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1340                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1341         return $evt if $evt;
1342
1343         my @not_allowed;
1344         for my $org (@$orgs) {
1345                 for my $perm (@$perms) {
1346                         if($apputils->check_perms($user_id, $org, $perm)) {
1347                                 push @not_allowed, [ $org, $perm ];
1348                         }
1349                 }
1350         }
1351
1352         return \@not_allowed
1353 }
1354
1355
1356 __PACKAGE__->register_method(
1357         method => 'check_user_perms3',
1358         api_name        => 'open-ils.actor.user.perm.highest_org',
1359         notes           => q/
1360                 Returns the highest org unit id at which a user has a given permission
1361                 If the requestor does not match the target user, the requestor must have
1362                 'VIEW_PERMISSION' rights at the home org unit of the target user
1363                 @param authtoken The login session key
1364                 @param userid The id of the user in question
1365                 @param perm The permission to check
1366                 @return The org unit highest in the org tree within which the user has
1367                 the requested permission
1368         /);
1369
1370 sub check_user_perms3 {
1371         my($self, $client, $authtoken, $user_id, $perm) = @_;
1372         my $e = new_editor(authtoken=>$authtoken);
1373         return $e->event unless $e->checkauth;
1374
1375         my $tree = $U->get_org_tree();
1376
1377     unless($e->requestor->id == $user_id) {
1378         my $user = $e->retrieve_actor_user($user_id)
1379             or return $e->event;
1380         return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1381             return $U->find_highest_perm_org($perm, $user_id, $user->home_ou, $tree );
1382     }
1383
1384     return $U->find_highest_perm_org($perm, $user_id, $e->requestor->ws_ou, $tree);
1385 }
1386
1387 __PACKAGE__->register_method(
1388         method => 'user_has_work_perm_at',
1389         api_name        => 'open-ils.actor.user.has_work_perm_at',
1390     authoritative => 1,
1391     signature => {
1392         desc => q/
1393             Returns a set of org unit IDs which represent the highest orgs in 
1394             the org tree where the user has the requested permission.  The
1395             purpose of this method is to return the smallest set of org units
1396             which represent the full expanse of the user's ability to perform
1397             the requested action.  The user whose perms this method should
1398             check is implied by the authtoken. /,
1399         params => [
1400                     {desc => 'authtoken', type => 'string'},
1401             {desc => 'permission name', type => 'string'},
1402             {desc => q/user id, optional.  If present, check perms for 
1403                 this user instead of the logged in user/, type => 'number'},
1404         ],
1405         return => {desc => 'An array of org IDs'}
1406     }
1407 );
1408
1409 sub user_has_work_perm_at {
1410     my($self, $conn, $auth, $perm, $user_id) = @_;
1411     my $e = new_editor(authtoken=>$auth);
1412     return $e->event unless $e->checkauth;
1413     if(defined $user_id) {
1414         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1415         return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1416     }
1417     return $U->user_has_work_perm_at($e, $perm, undef, $user_id);
1418 }
1419
1420 __PACKAGE__->register_method(
1421         method => 'user_has_work_perm_at_batch',
1422         api_name        => 'open-ils.actor.user.has_work_perm_at.batch',
1423     authoritative => 1,
1424 );
1425
1426 sub user_has_work_perm_at_batch {
1427     my($self, $conn, $auth, $perms, $user_id) = @_;
1428     my $e = new_editor(authtoken=>$auth);
1429     return $e->event unless $e->checkauth;
1430     if(defined $user_id) {
1431         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
1432         return $e->event unless $e->allowed('VIEW_PERMISSION', $user->home_ou);
1433     }
1434     my $map = {};
1435     $map->{$_} = $U->user_has_work_perm_at($e, $_) for @$perms;
1436     return $map;
1437 }
1438
1439
1440
1441 __PACKAGE__->register_method(
1442         method => 'check_user_perms4',
1443         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1444         notes           => q/
1445                 Returns the highest org unit id at which a user has a given permission
1446                 If the requestor does not match the target user, the requestor must have
1447                 'VIEW_PERMISSION' rights at the home org unit of the target user
1448                 @param authtoken The login session key
1449                 @param userid The id of the user in question
1450                 @param perms An array of perm names to check 
1451                 @return An array of orgId's  representing the org unit 
1452                 highest in the org tree within which the user has the requested permission
1453                 The arrah of orgId's has matches the order of the perms array
1454         /);
1455
1456 sub check_user_perms4 {
1457         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1458         
1459         my( $staff, $target, $org, $evt );
1460
1461         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1462                 $authtoken, $userid, 'VIEW_PERMISSION' );
1463         return $evt if $evt;
1464
1465         my @arr;
1466         return [] unless ref($perms);
1467         my $tree = $U->get_org_tree();
1468
1469         for my $p (@$perms) {
1470                 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1471         }
1472         return \@arr;
1473 }
1474
1475
1476
1477
1478 __PACKAGE__->register_method(
1479         method  => "user_fines_summary",
1480         api_name        => "open-ils.actor.user.fines.summary",
1481     authoritative => 1,
1482         notes           => <<"  NOTES");
1483         Returns a short summary of the users total open fines, excluding voided fines
1484         Params are login_session, user_id
1485         Returns a 'mous' object.
1486         NOTES
1487
1488 sub user_fines_summary {
1489         my( $self, $client, $auth, $user_id ) = @_;
1490         my $e = new_editor(authtoken=>$auth);
1491         return $e->event unless $e->checkauth;
1492         my $user = $e->retrieve_actor_user($user_id)
1493                 or return $e->event;
1494
1495         if( $user_id ne $e->requestor->id ) {
1496                 return $e->event unless 
1497                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1498         }
1499         
1500         # run this inside a transaction to prevent replication delay errors
1501         my $ses = $U->start_db_session();
1502         my $s = $ses->request(
1503                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1504         $U->rollback_db_session($ses);
1505         return $s;
1506 }
1507
1508
1509
1510
1511 __PACKAGE__->register_method(
1512         method  => "user_transactions",
1513         api_name        => "open-ils.actor.user.transactions",
1514         notes           => <<"  NOTES");
1515         Returns a list of open user transactions (mbts objects);
1516         Params are login_session, user_id
1517         Optional third parameter is the transactions type.  defaults to all
1518         NOTES
1519
1520 __PACKAGE__->register_method(
1521         method  => "user_transactions",
1522         api_name        => "open-ils.actor.user.transactions.have_charge",
1523         notes           => <<"  NOTES");
1524         Returns a list of all open user transactions (mbts objects) that have an initial charge
1525         Params are login_session, user_id
1526         Optional third parameter is the transactions type.  defaults to all
1527         NOTES
1528
1529 __PACKAGE__->register_method(
1530         method  => "user_transactions",
1531         api_name        => "open-ils.actor.user.transactions.have_balance",
1532     authoritative => 1,
1533         notes           => <<"  NOTES");
1534         Returns a list of all open user transactions (mbts objects) that have a balance
1535         Params are login_session, user_id
1536         Optional third parameter is the transactions type.  defaults to all
1537         NOTES
1538
1539 __PACKAGE__->register_method(
1540         method  => "user_transactions",
1541         api_name        => "open-ils.actor.user.transactions.fleshed",
1542         notes           => <<"  NOTES");
1543         Returns an object/hash of transaction, circ, title where transaction = an open 
1544         user transactions (mbts objects), circ is the attached circluation, and title
1545         is the title the circ points to
1546         Params are login_session, user_id
1547         Optional third parameter is the transactions type.  defaults to all
1548         NOTES
1549
1550 __PACKAGE__->register_method(
1551         method  => "user_transactions",
1552         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1553         notes           => <<"  NOTES");
1554         Returns an object/hash of transaction, circ, title where transaction = an open 
1555         user transactions that has an initial charge (mbts objects), circ is the 
1556         attached circluation, and title is the title the circ points to
1557         Params are login_session, user_id
1558         Optional third parameter is the transactions type.  defaults to all
1559         NOTES
1560
1561 __PACKAGE__->register_method(
1562         method  => "user_transactions",
1563         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1564     authoritative => 1,
1565         notes           => <<"  NOTES");
1566         Returns an object/hash of transaction, circ, title where transaction = an open 
1567         user transaction that has a balance (mbts objects), circ is the attached 
1568         circluation, and title is the title the circ points to
1569         Params are login_session, user_id
1570         Optional third parameter is the transaction type.  defaults to all
1571         NOTES
1572
1573 __PACKAGE__->register_method(
1574         method  => "user_transactions",
1575         api_name        => "open-ils.actor.user.transactions.count",
1576         notes           => <<"  NOTES");
1577         Returns an object/hash of transaction, circ, title where transaction = an open 
1578         user transactions (mbts objects), circ is the attached circluation, and title
1579         is the title the circ points to
1580         Params are login_session, user_id
1581         Optional third parameter is the transactions type.  defaults to all
1582         NOTES
1583
1584 __PACKAGE__->register_method(
1585         method  => "user_transactions",
1586         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1587         notes           => <<"  NOTES");
1588         Returns an object/hash of transaction, circ, title where transaction = an open 
1589         user transactions that has an initial charge (mbts objects), circ is the 
1590         attached circluation, and title is the title the circ points to
1591         Params are login_session, user_id
1592         Optional third parameter is the transactions type.  defaults to all
1593         NOTES
1594
1595 __PACKAGE__->register_method(
1596         method  => "user_transactions",
1597         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1598     authoritative => 1,
1599         notes           => <<"  NOTES");
1600         Returns an object/hash of transaction, circ, title where transaction = an open 
1601         user transaction that has a balance (mbts objects), circ is the attached 
1602         circluation, and title is the title the circ points to
1603         Params are login_session, user_id
1604         Optional third parameter is the transaction type.  defaults to all
1605         NOTES
1606
1607 __PACKAGE__->register_method(
1608         method  => "user_transactions",
1609         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1610     authoritative => 1,
1611         notes           => <<"  NOTES");
1612         Returns an object/hash of transaction, circ, title where transaction = an open 
1613         user transaction that has a balance (mbts objects), circ is the attached 
1614         circluation, and title is the title the circ points to
1615         Params are login_session, user_id
1616         Optional third parameter is the transaction type.  defaults to all
1617         NOTES
1618
1619
1620
1621 sub user_transactions {
1622         my( $self, $client, $login_session, $user_id, $type ) = @_;
1623
1624         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1625                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1626         return $evt if $evt;
1627
1628         my $api = $self->api_name();
1629         my $trans;
1630         my @xact;
1631
1632         if(defined($type)) { @xact = (xact_type =>  $type); 
1633
1634         } else { @xact = (); }
1635
1636         ($trans) = $self
1637                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1638                 ->run($login_session => $user_id => $type);
1639
1640
1641         if($api =~ /have_charge/o) {
1642
1643                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1644
1645         } elsif($api =~ /have_balance/o) {
1646
1647                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1648         } else {
1649
1650                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1651
1652         }
1653
1654         if($api =~ /total/o) { 
1655                 my $total = 0.0;
1656                 for my $t (@$trans) {
1657                         $total += $t->balance_owed;
1658                 }
1659
1660                 $logger->debug("Total balance owed by user $user_id: $total");
1661                 return $total;
1662         }
1663
1664         if($api =~ /count/o) { return scalar @$trans; }
1665         if($api !~ /fleshed/o) { return $trans; }
1666
1667         my @resp;
1668         for my $t (@$trans) {
1669                         
1670                 if( $t->xact_type ne 'circulation' ) {
1671                         push @resp, {transaction => $t};
1672                         next;
1673                 }
1674
1675                 my $circ = $apputils->simple_scalar_request(
1676                                 "open-ils.cstore",
1677                                 "open-ils.cstore.direct.action.circulation.retrieve",
1678                                 $t->id );
1679
1680                 next unless $circ;
1681
1682                 my $title = $apputils->simple_scalar_request(
1683                         "open-ils.storage", 
1684                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1685                         $circ->target_copy );
1686
1687                 next unless $title;
1688
1689                 my $u = OpenILS::Utils::ModsParser->new();
1690                 $u->start_mods_batch($title->marc());
1691                 my $mods = $u->finish_mods_batch();
1692                 $mods->doc_id($title->id) if $mods;
1693
1694                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1695
1696         }
1697
1698         return \@resp; 
1699
1700
1701
1702 __PACKAGE__->register_method(
1703         method  => "user_transaction_retrieve",
1704         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1705         argc            => 1,
1706         notes           => <<"  NOTES");
1707         Returns a fleshed transaction record
1708         NOTES
1709 __PACKAGE__->register_method(
1710         method  => "user_transaction_retrieve",
1711         api_name        => "open-ils.actor.user.transaction.retrieve",
1712         argc            => 1,
1713         notes           => <<"  NOTES");
1714         Returns a transaction record
1715         NOTES
1716 sub user_transaction_retrieve {
1717         my( $self, $client, $login_session, $bill_id ) = @_;
1718
1719         # I think I'm deprecated... make sure.   phasefx says, "No, I'll use you :)
1720
1721         my $trans = $apputils->simple_scalar_request( 
1722                 "open-ils.cstore",
1723                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1724                 $bill_id
1725         );
1726
1727         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1728                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1729         return $evt if $evt;
1730         
1731         my $api = $self->api_name();
1732         if($api !~ /fleshed/o) { return $trans; }
1733
1734         if( $trans->xact_type ne 'circulation' ) {
1735                 $logger->debug("Returning non-circ transaction");
1736                 return {transaction => $trans};
1737         }
1738
1739         my $circ = $apputils->simple_scalar_request(
1740                         "open-ils.cstore",
1741                         "open-ils.cstore.direct.action.circulation.retrieve",
1742                         $trans->id );
1743
1744         return {transaction => $trans} unless $circ;
1745         $logger->debug("Found the circ transaction");
1746
1747         my $title = $apputils->simple_scalar_request(
1748                 "open-ils.storage", 
1749                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1750                 $circ->target_copy );
1751
1752         return {transaction => $trans, circ => $circ } unless $title;
1753         $logger->debug("Found the circ title");
1754
1755         my $mods;
1756     my $copy = $apputils->simple_scalar_request(
1757         "open-ils.cstore",
1758         "open-ils.cstore.direct.asset.copy.retrieve",
1759         $circ->target_copy );
1760
1761         try {
1762                 my $u = OpenILS::Utils::ModsParser->new();
1763                 $u->start_mods_batch($title->marc());
1764                 $mods = $u->finish_mods_batch();
1765         } otherwise {
1766                 if ($title->id == OILS_PRECAT_RECORD) {
1767                         $mods = new Fieldmapper::metabib::virtual_record;
1768                         $mods->doc_id(OILS_PRECAT_RECORD);
1769                         $mods->title($copy->dummy_title);
1770                         $mods->author($copy->dummy_author);
1771                 }
1772         };
1773
1774         $logger->debug("MODSized the circ title");
1775
1776         return {transaction => $trans, circ => $circ, record => $mods, copy => $copy };
1777 }
1778
1779
1780 __PACKAGE__->register_method(
1781         method  => "hold_request_count",
1782         api_name        => "open-ils.actor.user.hold_requests.count",
1783     authoritative => 1,
1784         argc            => 1,
1785         notes           => <<"  NOTES");
1786         Returns hold ready/total counts
1787         NOTES
1788 sub hold_request_count {
1789         my( $self, $client, $login_session, $userid ) = @_;
1790
1791         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1792                 $login_session, $userid, 'VIEW_HOLD' );
1793         return $evt if $evt;
1794         
1795
1796         my $holds = $apputils->simple_scalar_request(
1797                         "open-ils.cstore",
1798                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1799                         { 
1800                                 usr => $userid,
1801                                 fulfillment_time => {"=" => undef },
1802                                 cancel_time => undef,
1803                         }
1804         );
1805
1806         my @ready;
1807         for my $h (@$holds) {
1808                 next unless $h->capture_time and $h->current_copy;
1809
1810                 my $copy = $apputils->simple_scalar_request(
1811                         "open-ils.cstore",
1812                         "open-ils.cstore.direct.asset.copy.retrieve",
1813                         $h->current_copy
1814                 );
1815
1816                 if ($copy and $copy->status == 8) {
1817                         push @ready, $h;
1818                 }
1819         }
1820
1821         return { total => scalar(@$holds), ready => scalar(@ready) };
1822 }
1823
1824
1825 __PACKAGE__->register_method(
1826         method  => "checkedout_count",
1827         api_name        => "open-ils.actor.user.checked_out.count__",
1828         argc            => 1,
1829         notes           => <<"  NOTES");
1830         Returns a transaction record
1831         NOTES
1832
1833 # XXX Deprecate Me
1834 sub checkedout_count {
1835         my( $self, $client, $login_session, $userid ) = @_;
1836
1837         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1838                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1839         return $evt if $evt;
1840         
1841         my $circs = $apputils->simple_scalar_request(
1842                         "open-ils.cstore",
1843                         "open-ils.cstore.direct.action.circulation.search.atomic",
1844                         { usr => $userid, stop_fines => undef }
1845                         #{ usr => $userid, checkin_time => {"=" => undef } }
1846         );
1847
1848         my $parser = DateTime::Format::ISO8601->new;
1849
1850         my (@out,@overdue);
1851         for my $c (@$circs) {
1852                 my $due_dt = $parser->parse_datetime( cleanse_ISO8601( $c->due_date ) );
1853                 my $due = $due_dt->epoch;
1854
1855                 if ($due < DateTime->today->epoch) {
1856                         push @overdue, $c;
1857                 }
1858         }
1859
1860         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1861 }
1862
1863
1864 __PACKAGE__->register_method(
1865         method          => "checked_out",
1866         api_name                => "open-ils.actor.user.checked_out",
1867     authoritative => 1,
1868         argc                    => 2,
1869         signature       => q/
1870                 Returns a structure of circulations objects sorted by
1871                 out, overdue, lost, claims_returned, long_overdue.
1872                 A list of IDs are returned of each type.
1873                 lost, long_overdue, and claims_returned circ will not
1874                 be "finished" (there is an outstanding balance or some 
1875                 other pending action on the circ). 
1876
1877                 The .count method also includes a 'total' field which 
1878                 sums all "open" circs
1879         /
1880 );
1881
1882 __PACKAGE__->register_method(
1883         method          => "checked_out",
1884         api_name                => "open-ils.actor.user.checked_out.count",
1885     authoritative => 1,
1886         argc                    => 2,
1887         signature       => q/@see open-ils.actor.user.checked_out/
1888 );
1889
1890 sub checked_out {
1891         my( $self, $conn, $auth, $userid ) = @_;
1892
1893         my $e = new_editor(authtoken=>$auth);
1894         return $e->event unless $e->checkauth;
1895
1896         if( $userid ne $e->requestor->id ) {
1897         my $user = $e->retrieve_actor_user($userid) or return $e->event;
1898                 unless($e->allowed('VIEW_CIRCULATIONS', $user->home_ou)) {
1899
1900             # see if there is a friend link allowing circ.view perms
1901             my $allowed = OpenILS::Application::Actor::Friends->friend_perm_allowed(
1902                 $e, $userid, $e->requestor->id, 'circ.view');
1903             return $e->event unless $allowed;
1904         }
1905         }
1906
1907         my $count = $self->api_name =~ /count/;
1908         return _checked_out( $count, $e, $userid );
1909 }
1910
1911 sub _checked_out {
1912         my( $iscount, $e, $userid ) = @_;
1913         my $meth = 'open-ils.storage.actor.user.checked_out';
1914         $meth = "$meth.count" if $iscount;
1915         return $U->storagereq($meth, $userid);
1916 }
1917
1918
1919 sub _checked_out_WHAT {
1920         my( $iscount, $e, $userid ) = @_;
1921
1922         my $circs = $e->search_action_circulation( 
1923                 { usr => $userid, stop_fines => undef });
1924
1925         my $mcircs = $e->search_action_circulation( 
1926                 { 
1927                         usr => $userid, 
1928                         checkin_time => undef, 
1929                         xact_finish => undef, 
1930                 });
1931
1932         
1933         push( @$circs, @$mcircs );
1934
1935         my $parser = DateTime::Format::ISO8601->new;
1936
1937         # split the circs up into overdue and not-overdue circs
1938         my (@out,@overdue);
1939         for my $c (@$circs) {
1940                 if( $c->due_date ) {
1941                         my $due_dt = $parser->parse_datetime( cleanse_ISO8601( $c->due_date ) );
1942                         my $due = $due_dt->epoch;
1943                         if ($due < DateTime->today->epoch) {
1944                                 push @overdue, $c->id;
1945                         } else {
1946                                 push @out, $c->id;
1947                         }
1948                 } else {
1949                         push @out, $c->id;
1950                 }
1951         }
1952
1953         # grab all of the lost, claims-returned, and longoverdue circs
1954         #my $open = $e->search_action_circulation(
1955         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1956
1957
1958         # these items have stop_fines, but no xact_finish, so money
1959         # is owed on them and they have not been checked in
1960         my $open = $e->search_action_circulation(
1961                 {
1962                         usr                             => $userid, 
1963                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1964                         xact_finish             => undef,
1965                         checkin_time    => undef,
1966                 }
1967         );
1968
1969
1970         my( @lost, @cr, @lo );
1971         for my $c (@$open) {
1972                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1973                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1974                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1975         }
1976
1977
1978         if( $iscount ) {
1979                 return {
1980                         total           => @$circs + @lost + @cr + @lo,
1981                         out             => scalar(@out),
1982                         overdue => scalar(@overdue),
1983                         lost            => scalar(@lost),
1984                         claims_returned => scalar(@cr),
1985                         long_overdue            => scalar(@lo)
1986                 };
1987         }
1988
1989         return {
1990                 out             => \@out,
1991                 overdue => \@overdue,
1992                 lost            => \@lost,
1993                 claims_returned => \@cr,
1994                 long_overdue            => \@lo
1995         };
1996 }
1997
1998
1999
2000 __PACKAGE__->register_method(
2001         method          => "checked_in_with_fines",
2002         api_name                => "open-ils.actor.user.checked_in_with_fines",
2003     authoritative => 1,
2004         argc                    => 2,
2005         signature       => q/@see open-ils.actor.user.checked_out/
2006 );
2007 sub checked_in_with_fines {
2008         my( $self, $conn, $auth, $userid ) = @_;
2009
2010         my $e = new_editor(authtoken=>$auth);
2011         return $e->event unless $e->checkauth;
2012
2013         if( $userid ne $e->requestor->id ) {
2014                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2015         }
2016
2017         # money is owed on these items and they are checked in
2018         my $open = $e->search_action_circulation(
2019                 {
2020                         usr                             => $userid, 
2021                         xact_finish             => undef,
2022                         checkin_time    => { "!=" => undef },
2023                 }
2024         );
2025
2026
2027         my( @lost, @cr, @lo );
2028         for my $c (@$open) {
2029                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2030                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2031                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2032         }
2033
2034         return {
2035                 lost            => \@lost,
2036                 claims_returned => \@cr,
2037                 long_overdue            => \@lo
2038         };
2039 }
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049 __PACKAGE__->register_method(
2050         method  => "user_transaction_history",
2051         api_name        => "open-ils.actor.user.transactions.history",
2052         argc            => 1,
2053         notes           => <<"  NOTES");
2054         Returns a list of billable transactions for a user, optionally by type
2055         NOTES
2056 __PACKAGE__->register_method(
2057         method  => "user_transaction_history",
2058         api_name        => "open-ils.actor.user.transactions.history.have_charge",
2059         argc            => 1,
2060         notes           => <<"  NOTES");
2061         Returns a list of billable transactions for a user that have an initial charge, optionally by type
2062         NOTES
2063 __PACKAGE__->register_method(
2064         method  => "user_transaction_history",
2065         api_name        => "open-ils.actor.user.transactions.history.have_balance",
2066     authoritative => 1,
2067         argc            => 1,
2068         notes           => <<"  NOTES");
2069         Returns a list of billable transactions for a user that have a balance, optionally by type
2070         NOTES
2071 __PACKAGE__->register_method(
2072         method  => "user_transaction_history",
2073         api_name        => "open-ils.actor.user.transactions.history.still_open",
2074         argc            => 1,
2075         notes           => <<"  NOTES");
2076         Returns a list of billable transactions for a user that are not finished
2077         NOTES
2078 __PACKAGE__->register_method(
2079         method  => "user_transaction_history",
2080         api_name        => "open-ils.actor.user.transactions.history.have_bill",
2081     authoritative => 1,
2082         argc            => 1,
2083         notes           => <<"  NOTES");
2084         Returns a list of billable transactions for a user that has billings
2085         NOTES
2086 __PACKAGE__->register_method(
2087         method  => "user_transaction_history",
2088         api_name        => "open-ils.actor.user.transactions.history.ids",
2089         argc            => 1,
2090         notes           => <<"  NOTES");
2091         Returns a list of billable transaction ids for a user, optionally by type
2092         NOTES
2093 __PACKAGE__->register_method(
2094         method  => "user_transaction_history",
2095         api_name        => "open-ils.actor.user.transactions.history.have_charge.ids",
2096         argc            => 1,
2097         notes           => <<"  NOTES");
2098         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
2099         NOTES
2100 __PACKAGE__->register_method(
2101         method  => "user_transaction_history",
2102         api_name        => "open-ils.actor.user.transactions.history.have_balance.ids",
2103     authoritative => 1,
2104         argc            => 1,
2105         notes           => <<"  NOTES");
2106         Returns a list of billable transaction ids for a user that have a balance, optionally by type
2107         NOTES
2108 __PACKAGE__->register_method(
2109         method  => "user_transaction_history",
2110         api_name        => "open-ils.actor.user.transactions.history.still_open.ids",
2111         argc            => 1,
2112         notes           => <<"  NOTES");
2113         Returns a list of billable transaction ids for a user that are not finished
2114         NOTES
2115 __PACKAGE__->register_method(
2116         method  => "user_transaction_history",
2117         api_name        => "open-ils.actor.user.transactions.history.have_bill.ids",
2118     authoritative => 1,
2119         argc            => 1,
2120         notes           => <<"  NOTES");
2121         Returns a list of billable transaction ids for a user that has billings
2122         NOTES
2123 __PACKAGE__->register_method(
2124         method  => "user_transaction_history",
2125         api_name        => "open-ils.actor.user.transactions.history.have_bill_or_payment",
2126     authoritative => 1,
2127         argc            => 1,
2128         notes           => <<"  NOTES");
2129         Returns a list of billable transactions for a user that has non-zero-sum billings or at least 1 payment
2130         NOTES
2131 __PACKAGE__->register_method(
2132         method  => "user_transaction_history",
2133         api_name        => "open-ils.actor.user.transactions.history.have_bill_or_payment.ids",
2134     authoritative => 1,
2135         argc            => 1,
2136         notes           => <<"  NOTES");
2137         Returns a list of billable transaction ids for a user that has non-zero-sum billings or at least 1 payment
2138         NOTES
2139
2140
2141
2142 sub user_transaction_history {
2143         my( $self, $conn, $auth, $userid, $type, $filter ) = @_;
2144     $filter ||= {};
2145
2146         # run inside of a transaction to prevent replication delays
2147         my $e = new_editor(authtoken=>$auth);
2148         return $e->die_event unless $e->checkauth;
2149
2150         if( $e->requestor->id ne $userid ) {
2151                 return $e->die_event 
2152                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2153         }
2154
2155         my $api = $self->api_name;
2156         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2157
2158     my $mbts = $e->search_money_billable_transaction_summary(
2159         [ 
2160             { usr => $userid, @xact_finish, %$filter },
2161             { order_by => { mbt => 'xact_start DESC' } }
2162         ]
2163     );
2164
2165         if(defined($type)) {
2166                 @$mbts = grep { $_->xact_type eq $type } @$mbts;
2167         }
2168
2169         if($api =~ /have_bill_or_payment/o) {
2170
2171         # transactions that have a non-zero sum across all billings or at least 1 payment
2172                 @$mbts = grep { 
2173             int($_->balance_owed * 100) != 0 ||
2174             defined($_->last_payment_ts) } @$mbts;
2175
2176     } elsif( $api =~ /have_balance/o) {
2177
2178         # transactions that have a non-zero overall balance
2179                 @$mbts = grep { int($_->balance_owed * 100) != 0 } @$mbts;
2180
2181         } elsif( $api =~ /have_charge/o) {
2182
2183         # transactions that have at least 1 billing, regardless of whether it was voided
2184                 @$mbts = grep { defined($_->last_billing_ts) } @$mbts;
2185
2186         } elsif( $api =~ /have_bill/o) {
2187
2188         # transactions that have non-zero sum across all billings.  This will exclude
2189         # xacts where all billings have been voided
2190                 @$mbts = grep { int($_->total_owed * 100) != 0 } @$mbts;
2191         }
2192
2193     if ($api =~ /\.ids/) {
2194         return [map {$_->id} @$mbts];
2195     } else {
2196         return $mbts;
2197     }
2198 }
2199
2200
2201
2202 __PACKAGE__->register_method(
2203         method  => "user_perms",
2204         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2205         argc            => 1,
2206         notes           => <<"  NOTES");
2207         Returns a list of permissions
2208         NOTES
2209 sub user_perms {
2210         my( $self, $client, $authtoken, $user ) = @_;
2211
2212         my( $staff, $evt ) = $apputils->checkses($authtoken);
2213         return $evt if $evt;
2214
2215         $user ||= $staff->id;
2216
2217         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2218                 return $evt;
2219         }
2220
2221         return $apputils->simple_scalar_request(
2222                 "open-ils.storage",
2223                 "open-ils.storage.permission.user_perms.atomic",
2224                 $user);
2225 }
2226
2227 __PACKAGE__->register_method(
2228         method  => "retrieve_perms",
2229         api_name        => "open-ils.actor.permissions.retrieve",
2230         notes           => <<"  NOTES");
2231         Returns a list of permissions
2232         NOTES
2233 sub retrieve_perms {
2234         my( $self, $client ) = @_;
2235         return $apputils->simple_scalar_request(
2236                 "open-ils.cstore",
2237                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2238                 { id => { '!=' => undef } }
2239         );
2240 }
2241
2242 __PACKAGE__->register_method(
2243         method  => "retrieve_groups",
2244         api_name        => "open-ils.actor.groups.retrieve",
2245         notes           => <<"  NOTES");
2246         Returns a list of user groupss
2247         NOTES
2248 sub retrieve_groups {
2249         my( $self, $client ) = @_;
2250         return new_editor()->retrieve_all_permission_grp_tree();
2251 }
2252
2253 __PACKAGE__->register_method(
2254         method  => "retrieve_org_address",
2255         api_name        => "open-ils.actor.org_unit.address.retrieve",
2256         notes           => <<'  NOTES');
2257         Returns an org_unit address by ID
2258         @param An org_address ID
2259         NOTES
2260 sub retrieve_org_address {
2261         my( $self, $client, $id ) = @_;
2262         return $apputils->simple_scalar_request(
2263                 "open-ils.cstore",
2264                 "open-ils.cstore.direct.actor.org_address.retrieve",
2265                 $id
2266         );
2267 }
2268
2269 __PACKAGE__->register_method(
2270         method  => "retrieve_groups_tree",
2271         api_name        => "open-ils.actor.groups.tree.retrieve",
2272         notes           => <<"  NOTES");
2273         Returns a list of user groups
2274         NOTES
2275 sub retrieve_groups_tree {
2276         my( $self, $client ) = @_;
2277         return new_editor()->search_permission_grp_tree(
2278                 [
2279                         { parent => undef},
2280                         {       
2281                                 flesh                           => -1,
2282                                 flesh_fields    => { pgt => ["children"] }, 
2283                                 order_by                        => { pgt => 'name'}
2284                         }
2285                 ]
2286         )->[0];
2287 }
2288
2289
2290 __PACKAGE__->register_method(
2291         method  => "add_user_to_groups",
2292         api_name        => "open-ils.actor.user.set_groups",
2293         notes           => <<"  NOTES");
2294         Adds a user to one or more permission groups
2295         NOTES
2296
2297 sub add_user_to_groups {
2298         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2299
2300         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2301                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2302         return $evt if $evt;
2303
2304         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2305                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2306         return $evt if $evt;
2307
2308         $apputils->simplereq(
2309                 'open-ils.storage',
2310                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2311                 
2312         for my $group (@$groups) {
2313                 my $link = Fieldmapper::permission::usr_grp_map->new;
2314                 $link->grp($group);
2315                 $link->usr($userid);
2316
2317                 my $id = $apputils->simplereq(
2318                         'open-ils.storage',
2319                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2320         }
2321
2322         return 1;
2323 }
2324
2325 __PACKAGE__->register_method(
2326         method  => "get_user_perm_groups",
2327         api_name        => "open-ils.actor.user.get_groups",
2328         notes           => <<"  NOTES");
2329         Retrieve a user's permission groups.
2330         NOTES
2331
2332
2333 sub get_user_perm_groups {
2334         my( $self, $client, $authtoken, $userid ) = @_;
2335
2336         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2337                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2338         return $evt if $evt;
2339
2340         return $apputils->simplereq(
2341                 'open-ils.cstore',
2342                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2343 }       
2344
2345
2346 __PACKAGE__->register_method(
2347         method  => "get_user_work_ous",
2348         api_name        => "open-ils.actor.user.get_work_ous",
2349         notes           => <<"  NOTES");
2350         Retrieve a user's work org units.
2351         NOTES
2352 __PACKAGE__->register_method(
2353         method  => "get_user_work_ous",
2354         api_name        => "open-ils.actor.user.get_work_ous.ids",
2355         notes           => <<"  NOTES");
2356         Retrieve a user's work org units.
2357         NOTES
2358
2359
2360 sub get_user_work_ous {
2361         my( $self, $client, $auth, $userid ) = @_;
2362     my $e = new_editor(authtoken=>$auth);
2363     return $e->event unless $e->checkauth;
2364     $userid ||= $e->requestor->id;
2365
2366     if($e->requestor->id != $userid) {
2367         my $user = $e->retrieve_actor_user($userid)
2368             or return $e->event;
2369         return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2370     }
2371
2372     return $e->search_permission_usr_work_ou_map({usr => $userid})
2373         unless $self->api_name =~ /.ids$/;
2374
2375     # client just wants a list of org IDs
2376     return $U->get_user_work_ou_ids($e, $userid);
2377 }       
2378
2379
2380
2381
2382 __PACKAGE__->register_method (
2383         method          => 'register_workstation',
2384         api_name                => 'open-ils.actor.workstation.register.override',
2385         signature       => q/@see open-ils.actor.workstation.register/);
2386
2387 __PACKAGE__->register_method (
2388         method          => 'register_workstation',
2389         api_name                => 'open-ils.actor.workstation.register',
2390         signature       => q/
2391                 Registers a new workstion in the system
2392                 @param authtoken The login session key
2393                 @param name The name of the workstation id
2394                 @param owner The org unit that owns this workstation
2395                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2396                 if the name is already in use.
2397         /);
2398
2399 sub register_workstation {
2400         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2401
2402         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2403         return $e->die_event unless $e->checkauth;
2404         return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2405         my $existing = $e->search_actor_workstation({name => $name})->[0];
2406
2407         if( $existing ) {
2408
2409                 if( $self->api_name =~ /override/o ) {
2410             # workstation with the given name exists.  
2411
2412             if($owner ne $existing->owning_lib) {
2413                 # if necessary, update the owning_lib of the workstation
2414
2415                 $logger->info("changing owning lib of workstation ".$existing->id.
2416                     " from ".$existing->owning_lib." to $owner");
2417                             return $e->die_event unless 
2418                     $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib); 
2419
2420                             return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner); 
2421
2422                 $existing->owning_lib($owner);
2423                             return $e->die_event unless $e->update_actor_workstation($existing);
2424
2425                 $e->commit;
2426
2427             } else {
2428                 $logger->info(  
2429                     "attempt to register an existing workstation.  returning existing ID");
2430             }
2431
2432             return $existing->id;
2433
2434                 } else {
2435                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2436                 }
2437         }
2438
2439         my $ws = Fieldmapper::actor::workstation->new;
2440         $ws->owning_lib($owner);
2441         $ws->name($name);
2442         $e->create_actor_workstation($ws) or return $e->die_event;
2443         $e->commit;
2444         return $ws->id; # note: editor sets the id on the new object for us
2445 }
2446
2447 __PACKAGE__->register_method (
2448         method          => 'workstation_list',
2449         api_name                => 'open-ils.actor.workstation.list',
2450         signature       => q/
2451                 Returns a list of workstations registered at the given location
2452                 @param authtoken The login session key
2453                 @param ids A list of org_unit.id's for the workstation owners
2454         /);
2455
2456 sub workstation_list {
2457         my( $self, $conn, $authtoken, @orgs ) = @_;
2458
2459         my $e = new_editor(authtoken=>$authtoken);
2460         return $e->event unless $e->checkauth;
2461     my %results;
2462
2463     for my $o (@orgs) {
2464             return $e->event 
2465             unless $e->allowed('REGISTER_WORKSTATION', $o);
2466         $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2467     }
2468     return \%results;
2469 }
2470
2471
2472
2473
2474
2475
2476
2477 __PACKAGE__->register_method (
2478         method          => 'fetch_patron_note',
2479         api_name                => 'open-ils.actor.note.retrieve.all',
2480     authoritative => 1,
2481         signature       => q/
2482                 Returns a list of notes for a given user
2483                 Requestor must have VIEW_USER permission if pub==false and
2484                 @param authtoken The login session key
2485                 @param args Hash of params including
2486                         patronid : the patron's id
2487                         pub : true if retrieving only public notes
2488         /
2489 );
2490
2491 sub fetch_patron_note {
2492         my( $self, $conn, $authtoken, $args ) = @_;
2493         my $patronid = $$args{patronid};
2494
2495         my($reqr, $evt) = $U->checkses($authtoken);
2496         return $evt if $evt;
2497
2498         my $patron;
2499         ($patron, $evt) = $U->fetch_user($patronid);
2500         return $evt if $evt;
2501
2502         if($$args{pub}) {
2503                 if( $patronid ne $reqr->id ) {
2504                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2505                         return $evt if $evt;
2506                 }
2507                 return $U->cstorereq(
2508                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2509                         { usr => $patronid, pub => 't' } );
2510         }
2511
2512         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2513         return $evt if $evt;
2514
2515         return $U->cstorereq(
2516                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2517 }
2518
2519 __PACKAGE__->register_method (
2520         method          => 'create_user_note',
2521         api_name                => 'open-ils.actor.note.create',
2522         signature       => q/
2523                 Creates a new note for the given user
2524                 @param authtoken The login session key
2525                 @param note The note object
2526         /
2527 );
2528 sub create_user_note {
2529         my( $self, $conn, $authtoken, $note ) = @_;
2530         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2531         return $e->die_event unless $e->checkauth;
2532
2533         my $user = $e->retrieve_actor_user($note->usr)
2534                 or return $e->die_event;
2535
2536         return $e->die_event unless 
2537                 $e->allowed('UPDATE_USER',$user->home_ou);
2538
2539         $note->creator($e->requestor->id);
2540         $e->create_actor_usr_note($note) or return $e->die_event;
2541         $e->commit;
2542         return $note->id;
2543 }
2544
2545
2546 __PACKAGE__->register_method (
2547         method          => 'delete_user_note',
2548         api_name                => 'open-ils.actor.note.delete',
2549         signature       => q/
2550                 Deletes a note for the given user
2551                 @param authtoken The login session key
2552                 @param noteid The note id
2553         /
2554 );
2555 sub delete_user_note {
2556         my( $self, $conn, $authtoken, $noteid ) = @_;
2557
2558         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2559         return $e->die_event unless $e->checkauth;
2560         my $note = $e->retrieve_actor_usr_note($noteid)
2561                 or return $e->die_event;
2562         my $user = $e->retrieve_actor_user($note->usr)
2563                 or return $e->die_event;
2564         return $e->die_event unless 
2565                 $e->allowed('UPDATE_USER', $user->home_ou);
2566         
2567         $e->delete_actor_usr_note($note) or return $e->die_event;
2568         $e->commit;
2569         return 1;
2570 }
2571
2572
2573 __PACKAGE__->register_method (
2574         method          => 'update_user_note',
2575         api_name                => 'open-ils.actor.note.update',
2576         signature       => q/
2577                 @param authtoken The login session key
2578                 @param note The note
2579         /
2580 );
2581
2582 sub update_user_note {
2583         my( $self, $conn, $auth, $note ) = @_;
2584         my $e = new_editor(authtoken=>$auth, xact=>1);
2585         return $e->event unless $e->checkauth;
2586         my $patron = $e->retrieve_actor_user($note->usr)
2587                 or return $e->event;
2588         return $e->event unless 
2589                 $e->allowed('UPDATE_USER', $patron->home_ou);
2590         $e->update_actor_user_note($note)
2591                 or return $e->event;
2592         $e->commit;
2593         return 1;
2594 }
2595
2596
2597
2598
2599 __PACKAGE__->register_method (
2600         method          => 'create_closed_date',
2601         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2602         signature       => q/
2603                 Creates a new closing entry for the given org_unit
2604                 @param authtoken The login session key
2605                 @param note The closed_date object
2606         /
2607 );
2608 sub create_closed_date {
2609         my( $self, $conn, $authtoken, $cd ) = @_;
2610
2611         my( $user, $evt ) = $U->checkses($authtoken);
2612         return $evt if $evt;
2613
2614         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2615         return $evt if $evt;
2616
2617         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2618
2619         my $id = $U->storagereq(
2620                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2621         return $U->DB_UPDATE_FAILED($cd) unless $id;
2622         return $id;
2623 }
2624
2625
2626 __PACKAGE__->register_method (
2627         method          => 'delete_closed_date',
2628         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2629         signature       => q/
2630                 Deletes a closing entry for the given org_unit
2631                 @param authtoken The login session key
2632                 @param noteid The close_date id
2633         /
2634 );
2635 sub delete_closed_date {
2636         my( $self, $conn, $authtoken, $cd ) = @_;
2637
2638         my( $user, $evt ) = $U->checkses($authtoken);
2639         return $evt if $evt;
2640
2641         my $cd_obj;
2642         ($cd_obj, $evt) = fetch_closed_date($cd);
2643         return $evt if $evt;
2644
2645         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2646         return $evt if $evt;
2647
2648         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2649
2650         my $stat = $U->storagereq(
2651                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2652         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2653         return $stat;
2654 }
2655
2656
2657 __PACKAGE__->register_method(
2658         method => 'usrname_exists',
2659         api_name        => 'open-ils.actor.username.exists',
2660         signature => q/
2661                 Returns 1 if the requested username exists, returns 0 otherwise
2662         /
2663 );
2664
2665 sub usrname_exists {
2666         my( $self, $conn, $auth, $usrname ) = @_;
2667         my $e = new_editor(authtoken=>$auth);
2668         return $e->event unless $e->checkauth;
2669         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2670         return $$a[0] if $a and @$a;
2671         return undef;
2672 }
2673
2674 __PACKAGE__->register_method(
2675         method => 'barcode_exists',
2676         api_name        => 'open-ils.actor.barcode.exists',
2677     authoritative => 1,
2678         signature => q/
2679                 Returns 1 if the requested barcode exists, returns 0 otherwise
2680         /
2681 );
2682
2683 sub barcode_exists {
2684         my( $self, $conn, $auth, $barcode ) = @_;
2685         my $e = new_editor(authtoken=>$auth);
2686         return $e->event unless $e->checkauth;
2687         my $card = $e->search_actor_card({barcode => $barcode});
2688         if (@$card) {
2689                 return 1;
2690         } else {
2691                 return 0;
2692         }
2693         #return undef unless @$card;
2694         #return $card->[0]->usr;
2695 }
2696
2697
2698 __PACKAGE__->register_method(
2699         method => 'retrieve_net_levels',
2700         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2701 );
2702
2703 sub retrieve_net_levels {
2704         my( $self, $conn, $auth ) = @_;
2705         my $e = new_editor(authtoken=>$auth);
2706         return $e->event unless $e->checkauth;
2707         return $e->retrieve_all_config_net_access_level();
2708 }
2709
2710 # Retain the old typo API name just in case
2711 __PACKAGE__->register_method(
2712         method => 'fetch_org_by_shortname',
2713         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2714 );
2715 __PACKAGE__->register_method(
2716         method => 'fetch_org_by_shortname',
2717         api_name => 'open-ils.actor.org_unit.retrieve_by_shortname',
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