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