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