]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
768d7e5aadcece92553b639f118fd57460d456bd
[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 ($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 || !grep { $_->perm eq $map->perm and $U->is_true($_->grantable) and $_->depth <= $map->depth } @$perms);
859
860                 #warn( "Updating permissions with method $method and session $ses and map $map" );
861                 $logger->info( "Updating permissions with method $method and map $map" );
862
863                 my $stat = $session->request($method, $map)->gather(1);
864                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
865
866         }
867
868         $apputils->commit_db_session($session);
869
870         return scalar(@$maps);
871 }
872
873
874 sub _create_standing_penalties {
875
876         my($session, $user_session, $patron, $new_patron) = @_;
877
878         my $maps = $patron->standing_penalties;
879         my $method;
880
881         for my $map (@$maps) {
882
883                 if ($map->isdeleted()) {
884                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
885                 } elsif ($map->isnew()) {
886                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
887                         $map->clear_id;
888                 } else {
889                         next;
890                 }
891
892                 $map->usr($new_patron->id);
893
894                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
895
896                 my $stat = $session->request($method, $map)->gather(1);
897                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
898         }
899
900         return ($new_patron, undef);
901 }
902
903
904
905 __PACKAGE__->register_method(
906         method  => "search_username",
907         api_name        => "open-ils.actor.user.search.username",
908 );
909
910 sub search_username {
911         my($self, $client, $username) = @_;
912     return new_editor()->search_actor_user({usrname=>$username});
913 }
914
915
916
917
918 __PACKAGE__->register_method(
919         method  => "user_retrieve_by_barcode",
920     authoritative => 1,
921         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
922
923 sub user_retrieve_by_barcode {
924         my($self, $client, $user_session, $barcode) = @_;
925
926         $logger->debug("Searching for user with barcode $barcode");
927         my ($user_obj, $evt) = $apputils->checkses($user_session);
928         return $evt if $evt;
929
930         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
931                         "open-ils.cstore", 
932                         "open-ils.cstore.direct.actor.card.search.atomic",
933                         { barcode => $barcode }
934         );
935
936         if(!$card || !$card->[0]) {
937                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
938         }
939
940         $card = $card->[0];
941         my $user = flesh_user($card->usr());
942
943         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
944         return $evt if $evt;
945
946         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
947         return $user;
948
949 }
950
951
952
953 __PACKAGE__->register_method(
954         method  => "get_user_by_id",
955         api_name        => "open-ils.actor.user.retrieve",);
956
957 sub get_user_by_id {
958         my ($self, $client, $auth, $id) = @_;
959         my $e = new_editor(authtoken=>$auth);
960         return $e->event unless $e->checkauth;
961         my $user = $e->retrieve_actor_user($id)
962                 or return $e->event;
963         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
964         return $user;
965 }
966
967
968
969 __PACKAGE__->register_method(
970         method  => "get_org_types",
971         api_name        => "open-ils.actor.org_types.retrieve",);
972
973 sub get_org_types {
974     return $U->get_org_types();
975 }
976
977
978
979 __PACKAGE__->register_method(
980         method  => "get_user_ident_types",
981         api_name        => "open-ils.actor.user.ident_types.retrieve",
982 );
983 my $ident_types;
984 sub get_user_ident_types {
985         return $ident_types if $ident_types;
986         return $ident_types = 
987                 new_editor()->retrieve_all_config_identification_type();
988 }
989
990
991
992
993 __PACKAGE__->register_method(
994         method  => "get_org_unit",
995         api_name        => "open-ils.actor.org_unit.retrieve",
996 );
997
998 sub get_org_unit {
999         my( $self, $client, $user_session, $org_id ) = @_;
1000         my $e = new_editor(authtoken => $user_session);
1001         if(!$org_id) {
1002                 return $e->event unless $e->checkauth;
1003                 $org_id = $e->requestor->ws_ou;
1004         }
1005         my $o = $e->retrieve_actor_org_unit($org_id)
1006                 or return $e->event;
1007         return $o;
1008 }
1009
1010 __PACKAGE__->register_method(
1011         method  => "search_org_unit",
1012         api_name        => "open-ils.actor.org_unit_list.search",
1013 );
1014
1015 sub search_org_unit {
1016
1017         my( $self, $client, $field, $value ) = @_;
1018
1019         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
1020                 "open-ils.cstore",
1021                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
1022                 { $field => $value } );
1023
1024         return $list;
1025 }
1026
1027
1028 # build the org tree
1029
1030 __PACKAGE__->register_method(
1031         method  => "get_org_tree",
1032         api_name        => "open-ils.actor.org_tree.retrieve",
1033         argc            => 0, 
1034         note            => "Returns the entire org tree structure",
1035 );
1036
1037 sub get_org_tree {
1038         my $self = shift;
1039         my $client = shift;
1040     return $U->get_org_tree($client->session->session_locale);
1041 }
1042
1043
1044 __PACKAGE__->register_method(
1045         method  => "get_org_descendants",
1046         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1047 );
1048
1049 # depth is optional.  org_unit is the id
1050 sub get_org_descendants {
1051         my( $self, $client, $org_unit, $depth ) = @_;
1052
1053     if(ref $org_unit eq 'ARRAY') {
1054         $depth ||= [];
1055         my @trees;
1056         for my $i (0..scalar(@$org_unit)-1) {
1057             my $list = $U->simple_scalar_request(
1058                             "open-ils.storage", 
1059                             "open-ils.storage.actor.org_unit.descendants.atomic",
1060                             $org_unit->[$i], $depth->[$i] );
1061             push(@trees, $U->build_org_tree($list));
1062         }
1063         return \@trees;
1064
1065     } else {
1066             my $orglist = $apputils->simple_scalar_request(
1067                             "open-ils.storage", 
1068                             "open-ils.storage.actor.org_unit.descendants.atomic",
1069                             $org_unit, $depth );
1070             return $U->build_org_tree($orglist);
1071     }
1072 }
1073
1074
1075 __PACKAGE__->register_method(
1076         method  => "get_org_ancestors",
1077         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1078 );
1079
1080 # depth is optional.  org_unit is the id
1081 sub get_org_ancestors {
1082         my( $self, $client, $org_unit, $depth ) = @_;
1083         my $orglist = $apputils->simple_scalar_request(
1084                         "open-ils.storage", 
1085                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1086                         $org_unit, $depth );
1087         return $U->build_org_tree($orglist);
1088 }
1089
1090
1091 __PACKAGE__->register_method(
1092         method  => "get_standings",
1093         api_name        => "open-ils.actor.standings.retrieve"
1094 );
1095
1096 my $user_standings;
1097 sub get_standings {
1098         return $user_standings if $user_standings;
1099         return $user_standings = 
1100                 $apputils->simple_scalar_request(
1101                         "open-ils.cstore",
1102                         "open-ils.cstore.direct.config.standing.search.atomic",
1103                         { id => { "!=" => undef } }
1104                 );
1105 }
1106
1107
1108
1109 __PACKAGE__->register_method(
1110         method  => "get_my_org_path",
1111         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1112 );
1113
1114 sub get_my_org_path {
1115         my( $self, $client, $auth, $org_id ) = @_;
1116         my $e = new_editor(authtoken=>$auth);
1117         return $e->event unless $e->checkauth;
1118         $org_id = $e->requestor->ws_ou unless defined $org_id;
1119
1120         return $apputils->simple_scalar_request(
1121                 "open-ils.storage",
1122                 "open-ils.storage.actor.org_unit.full_path.atomic",
1123                 $org_id );
1124 }
1125
1126
1127 __PACKAGE__->register_method(
1128         method  => "patron_adv_search",
1129         api_name        => "open-ils.actor.patron.search.advanced" );
1130 sub patron_adv_search {
1131         my( $self, $client, $auth, $search_hash, 
1132         $search_limit, $search_sort, $include_inactive, $search_depth ) = @_;
1133
1134         my $e = new_editor(authtoken=>$auth);
1135         return $e->event unless $e->checkauth;
1136         return $e->event unless $e->allowed('VIEW_USER');
1137         return $U->storagereq(
1138                 "open-ils.storage.actor.user.crazy_search", $search_hash, 
1139             $search_limit, $search_sort, $include_inactive, $e->requestor->ws_ou, $search_depth);
1140 }
1141
1142
1143 __PACKAGE__->register_method(
1144         method  => "update_passwd",
1145     authoritative => 1,
1146         api_name        => "open-ils.actor.user.password.update");
1147
1148 __PACKAGE__->register_method(
1149         method  => "update_passwd",
1150         api_name        => "open-ils.actor.user.username.update");
1151
1152 __PACKAGE__->register_method(
1153         method  => "update_passwd",
1154         api_name        => "open-ils.actor.user.email.update");
1155
1156 sub update_passwd {
1157     my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1158     my $e = new_editor(xact=>1, authtoken=>$auth);
1159     return $e->die_event unless $e->checkauth;
1160
1161     my $db_user = $e->retrieve_actor_user($e->requestor->id)
1162         or return $e->die_event;
1163     my $api = $self->api_name;
1164
1165     if( $api =~ /password/o ) {
1166
1167         # make sure the original password matches the in-database password
1168         return OpenILS::Event->new('INCORRECT_PASSWORD')
1169             if md5_hex($orig_pw) ne $db_user->passwd;
1170         $db_user->passwd($new_val);
1171
1172     } else {
1173
1174         # if we don't clear the password, the user will be updated with
1175         # a hashed version of the hashed version of their password
1176         $db_user->clear_passwd;
1177
1178         if( $api =~ /username/o ) {
1179
1180             # make sure no one else has this username
1181             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
1182                         return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1183             $db_user->usrname($new_val);
1184
1185         } elsif( $api =~ /email/o ) {
1186             $db_user->email($new_val);
1187         }
1188     }
1189
1190     $e->update_actor_user($db_user) or return $e->die_event;
1191     $e->commit;
1192     return 1;
1193 }
1194
1195
1196
1197
1198 __PACKAGE__->register_method(
1199         method  => "check_user_perms",
1200         api_name        => "open-ils.actor.user.perm.check",
1201         notes           => <<"  NOTES");
1202         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1203         perm type, if the user does *not* have the given permission it is added
1204         to a list which is returned from the method.  If all permissions
1205         are allowed, an empty list is returned
1206         if the logged in user does not match 'user_id', then the logged in user must
1207         have VIEW_PERMISSION priveleges.
1208         NOTES
1209
1210 sub check_user_perms {
1211         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1212
1213         my( $staff, $evt ) = $apputils->checkses($login_session);
1214         return $evt if $evt;
1215
1216         if($staff->id ne $user_id) {
1217                 if( $evt = $apputils->check_perms(
1218                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1219                         return $evt;
1220                 }
1221         }
1222
1223         my @not_allowed;
1224         for my $perm (@$perm_types) {
1225                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1226                         push @not_allowed, $perm;
1227                 }
1228         }
1229
1230         return \@not_allowed
1231 }
1232
1233 __PACKAGE__->register_method(
1234         method  => "check_user_perms2",
1235         api_name        => "open-ils.actor.user.perm.check.multi_org",
1236         notes           => q/
1237                 Checks the permissions on a list of perms and orgs for a user
1238                 @param authtoken The login session key
1239                 @param user_id The id of the user to check
1240                 @param orgs The array of org ids
1241                 @param perms The array of permission names
1242                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1243                 if the logged in user does not match 'user_id', then the logged in user must
1244                 have VIEW_PERMISSION priveleges.
1245         /);
1246
1247 sub check_user_perms2 {
1248         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1249
1250         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1251                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1252         return $evt if $evt;
1253
1254         my @not_allowed;
1255         for my $org (@$orgs) {
1256                 for my $perm (@$perms) {
1257                         if($apputils->check_perms($user_id, $org, $perm)) {
1258                                 push @not_allowed, [ $org, $perm ];
1259                         }
1260                 }
1261         }
1262
1263         return \@not_allowed
1264 }
1265
1266
1267 __PACKAGE__->register_method(
1268         method => 'check_user_perms3',
1269         api_name        => 'open-ils.actor.user.perm.highest_org',
1270         notes           => q/
1271                 Returns the highest org unit id at which a user has a given permission
1272                 If the requestor does not match the target user, the requestor must have
1273                 'VIEW_PERMISSION' rights at the home org unit of the target user
1274                 @param authtoken The login session key
1275                 @param userid The id of the user in question
1276                 @param perm The permission to check
1277                 @return The org unit highest in the org tree within which the user has
1278                 the requested permission
1279         /);
1280
1281 sub check_user_perms3 {
1282         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1283
1284         my( $staff, $target, $org, $evt );
1285
1286         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1287                 $authtoken, $userid, 'VIEW_PERMISSION' );
1288         return $evt if $evt;
1289
1290         my $tree = $U->get_org_tree();
1291         return $U->find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1292 }
1293
1294
1295 __PACKAGE__->register_method(
1296         method => 'check_user_work_perms',
1297         api_name        => 'open-ils.actor.user.work_perm.highest_org_set',
1298     authoritative => 1,
1299     signature => {
1300         desc => q/
1301             Returns a set of org units which represent the highest orgs in 
1302             the org tree where the user has the requested permission.  The
1303             purpose of this method is to return the smallest set of org units
1304             which represent the full expanse of the user's ability to perform
1305             the requested action.  The user whose perms this method should
1306             check is implied by the authtoken. /,
1307         params => [
1308                     {desc => 'authtoken', type => 'string'},
1309             {desc => 'permission name', type => 'string'},
1310             {desc => 'options hash, including "descendants", which will include all child orgs of the found perm orgs', type => 'hash'}
1311         ],
1312         return => {desc => 'An array of org IDs'}
1313     }
1314 );
1315
1316 __PACKAGE__->register_method(
1317         method => 'check_user_work_perms',
1318         api_name        => 'open-ils.actor.user.work_perm.org_tree_list',
1319     authoritative => 1,
1320     signature => q/
1321         @see open-ils.actor.user.work_perm.highest_org_set
1322         Returns a list of org trees.  The root of each tree
1323         is the highest org in the organization hierarchy where the user has the
1324         requested permission.  Below each tree root is its full tree of descendants.  
1325     /
1326 );
1327
1328 __PACKAGE__->register_method(
1329         method => 'check_user_work_perms',
1330         api_name        => 'open-ils.actor.user.work_perm.org_unit_list',
1331     authoritative => 1,
1332     signature => q/
1333         @see open-ils.actor.user.work_perm.highest_org_set
1334         Returns a list of list of all of the org_units where the user
1335         has the requested permission.  The first item in each list
1336         is the highest permission org for that section of the
1337         org tree.  The remaining items in each sub-list are the 
1338         descendants of that org.
1339
1340     /
1341 );
1342
1343 __PACKAGE__->register_method(
1344         method => 'check_user_work_perms',
1345         api_name        => 'open-ils.actor.user.work_perm.org_id_list',
1346     authoritative => 1,
1347     signature => q/
1348         @see open-ils.actor.user.work_perm.highest_org_set
1349         Returns a list of lists of all of the org_unit IDs where the user
1350         has the requested permission.  The first item in each list
1351         is the highest permission org for that section of the
1352         org tree.  The remaining items in each sub-list are the 
1353         descendants of that org.
1354     /
1355 );
1356
1357 sub check_user_work_perms {
1358     my($self, $conn, $auth, $perm, $options) = @_;
1359     my $e = new_editor(authtoken=>$auth);
1360     return $e->event unless $e->checkauth;
1361     my $orglist = $U->find_highest_work_orgs($e, $perm, $options);
1362
1363     return $orglist if $self->api_name =~ /highest_org_set/;
1364
1365     # build a list of org trees
1366     return get_org_descendants($self, $conn, $orglist)
1367         if $self->api_name =~ /org_tree_list/;
1368
1369     my @list;
1370     for my $orgid (@$orglist) {
1371         my @sublist = grep {$_ ne $orgid} @{$U->get_org_descendants($orgid)};
1372         unshift @sublist, $orgid; # make sure it's at the front of the list
1373         if($self->api_name =~ /org_id_list/) {
1374             push(@list, @sublist);
1375         } else {
1376             push(@list, @{$e->batch_retrieve_actor_org_unit(\@sublist)});
1377         }
1378     }
1379
1380     return \@list;
1381 }
1382
1383
1384 __PACKAGE__->register_method(
1385         method => 'check_user_perms4',
1386         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1387         notes           => q/
1388                 Returns the highest org unit id at which a user has a given permission
1389                 If the requestor does not match the target user, the requestor must have
1390                 'VIEW_PERMISSION' rights at the home org unit of the target user
1391                 @param authtoken The login session key
1392                 @param userid The id of the user in question
1393                 @param perms An array of perm names to check 
1394                 @return An array of orgId's  representing the org unit 
1395                 highest in the org tree within which the user has the requested permission
1396                 The arrah of orgId's has matches the order of the perms array
1397         /);
1398
1399 sub check_user_perms4 {
1400         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1401         
1402         my( $staff, $target, $org, $evt );
1403
1404         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1405                 $authtoken, $userid, 'VIEW_PERMISSION' );
1406         return $evt if $evt;
1407
1408         my @arr;
1409         return [] unless ref($perms);
1410         my $tree = $U->get_org_tree();
1411
1412         for my $p (@$perms) {
1413                 push( @arr, $U->find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1414         }
1415         return \@arr;
1416 }
1417
1418
1419
1420
1421 __PACKAGE__->register_method(
1422         method  => "user_fines_summary",
1423         api_name        => "open-ils.actor.user.fines.summary",
1424     authoritative => 1,
1425         notes           => <<"  NOTES");
1426         Returns a short summary of the users total open fines, excluding voided fines
1427         Params are login_session, user_id
1428         Returns a 'mous' object.
1429         NOTES
1430
1431 sub user_fines_summary {
1432         my( $self, $client, $auth, $user_id ) = @_;
1433         my $e = new_editor(authtoken=>$auth);
1434         return $e->event unless $e->checkauth;
1435         my $user = $e->retrieve_actor_user($user_id)
1436                 or return $e->event;
1437
1438         if( $user_id ne $e->requestor->id ) {
1439                 return $e->event unless 
1440                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1441         }
1442         
1443         # run this inside a transaction to prevent replication delay errors
1444         my $ses = $U->start_db_session();
1445         my $s = $ses->request(
1446                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1447         $U->rollback_db_session($ses);
1448         return $s;
1449 }
1450
1451
1452
1453
1454 __PACKAGE__->register_method(
1455         method  => "user_transactions",
1456         api_name        => "open-ils.actor.user.transactions",
1457         notes           => <<"  NOTES");
1458         Returns a list of open user transactions (mbts objects);
1459         Params are login_session, user_id
1460         Optional third parameter is the transactions type.  defaults to all
1461         NOTES
1462
1463 __PACKAGE__->register_method(
1464         method  => "user_transactions",
1465         api_name        => "open-ils.actor.user.transactions.have_charge",
1466         notes           => <<"  NOTES");
1467         Returns a list of all open user transactions (mbts objects) that have an initial charge
1468         Params are login_session, user_id
1469         Optional third parameter is the transactions type.  defaults to all
1470         NOTES
1471
1472 __PACKAGE__->register_method(
1473         method  => "user_transactions",
1474         api_name        => "open-ils.actor.user.transactions.have_balance",
1475         notes           => <<"  NOTES");
1476         Returns a list of all open user transactions (mbts objects) that have a balance
1477         Params are login_session, user_id
1478         Optional third parameter is the transactions type.  defaults to all
1479         NOTES
1480
1481 __PACKAGE__->register_method(
1482         method  => "user_transactions",
1483         api_name        => "open-ils.actor.user.transactions.fleshed",
1484         notes           => <<"  NOTES");
1485         Returns an object/hash of transaction, circ, title where transaction = an open 
1486         user transactions (mbts objects), circ is the attached circluation, and title
1487         is the title the circ points to
1488         Params are login_session, user_id
1489         Optional third parameter is the transactions type.  defaults to all
1490         NOTES
1491
1492 __PACKAGE__->register_method(
1493         method  => "user_transactions",
1494         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1495         notes           => <<"  NOTES");
1496         Returns an object/hash of transaction, circ, title where transaction = an open 
1497         user transactions that has an initial charge (mbts objects), circ is the 
1498         attached circluation, and title is the title the circ points to
1499         Params are login_session, user_id
1500         Optional third parameter is the transactions type.  defaults to all
1501         NOTES
1502
1503 __PACKAGE__->register_method(
1504         method  => "user_transactions",
1505         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1506         notes           => <<"  NOTES");
1507         Returns an object/hash of transaction, circ, title where transaction = an open 
1508         user transaction that has a balance (mbts objects), circ is the attached 
1509         circluation, and title is the title the circ points to
1510         Params are login_session, user_id
1511         Optional third parameter is the transaction type.  defaults to all
1512         NOTES
1513
1514 __PACKAGE__->register_method(
1515         method  => "user_transactions",
1516         api_name        => "open-ils.actor.user.transactions.count",
1517         notes           => <<"  NOTES");
1518         Returns an object/hash of transaction, circ, title where transaction = an open 
1519         user transactions (mbts objects), circ is the attached circluation, and title
1520         is the title the circ points to
1521         Params are login_session, user_id
1522         Optional third parameter is the transactions type.  defaults to all
1523         NOTES
1524
1525 __PACKAGE__->register_method(
1526         method  => "user_transactions",
1527         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1528         notes           => <<"  NOTES");
1529         Returns an object/hash of transaction, circ, title where transaction = an open 
1530         user transactions that has an initial charge (mbts objects), circ is the 
1531         attached circluation, and title is the title the circ points to
1532         Params are login_session, user_id
1533         Optional third parameter is the transactions type.  defaults to all
1534         NOTES
1535
1536 __PACKAGE__->register_method(
1537         method  => "user_transactions",
1538         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1539         notes           => <<"  NOTES");
1540         Returns an object/hash of transaction, circ, title where transaction = an open 
1541         user transaction that has a balance (mbts objects), circ is the attached 
1542         circluation, and title is the title the circ points to
1543         Params are login_session, user_id
1544         Optional third parameter is the transaction type.  defaults to all
1545         NOTES
1546
1547 __PACKAGE__->register_method(
1548         method  => "user_transactions",
1549         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1550         notes           => <<"  NOTES");
1551         Returns an object/hash of transaction, circ, title where transaction = an open 
1552         user transaction that has a balance (mbts objects), circ is the attached 
1553         circluation, and title is the title the circ points to
1554         Params are login_session, user_id
1555         Optional third parameter is the transaction type.  defaults to all
1556         NOTES
1557
1558
1559
1560 sub user_transactions {
1561         my( $self, $client, $login_session, $user_id, $type ) = @_;
1562
1563         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1564                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1565         return $evt if $evt;
1566
1567         my $api = $self->api_name();
1568         my $trans;
1569         my @xact;
1570
1571         if(defined($type)) { @xact = (xact_type =>  $type); 
1572
1573         } else { @xact = (); }
1574
1575         ($trans) = $self
1576                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1577                 ->run($login_session => $user_id => $type);
1578
1579         if($api =~ /have_charge/o) {
1580
1581                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1582
1583         } elsif($api =~ /have_balance/o) {
1584
1585                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1586         } else {
1587
1588                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1589
1590         }
1591
1592         if($api =~ /total/o) { 
1593                 my $total = 0.0;
1594                 for my $t (@$trans) {
1595                         $total += $t->balance_owed;
1596                 }
1597
1598                 $logger->debug("Total balance owed by user $user_id: $total");
1599                 return $total;
1600         }
1601
1602         if($api =~ /count/o) { return scalar @$trans; }
1603         if($api !~ /fleshed/o) { return $trans; }
1604
1605         my @resp;
1606         for my $t (@$trans) {
1607                         
1608                 if( $t->xact_type ne 'circulation' ) {
1609                         push @resp, {transaction => $t};
1610                         next;
1611                 }
1612
1613                 my $circ = $apputils->simple_scalar_request(
1614                                 "open-ils.cstore",
1615                                 "open-ils.cstore.direct.action.circulation.retrieve",
1616                                 $t->id );
1617
1618                 next unless $circ;
1619
1620                 my $title = $apputils->simple_scalar_request(
1621                         "open-ils.storage", 
1622                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1623                         $circ->target_copy );
1624
1625                 next unless $title;
1626
1627                 my $u = OpenILS::Utils::ModsParser->new();
1628                 $u->start_mods_batch($title->marc());
1629                 my $mods = $u->finish_mods_batch();
1630                 $mods->doc_id($title->id) if $mods;
1631
1632                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1633
1634         }
1635
1636         return \@resp; 
1637
1638
1639
1640 __PACKAGE__->register_method(
1641         method  => "user_transaction_retrieve",
1642         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1643         argc            => 1,
1644         notes           => <<"  NOTES");
1645         Returns a fleshedtransaction record
1646         NOTES
1647 __PACKAGE__->register_method(
1648         method  => "user_transaction_retrieve",
1649         api_name        => "open-ils.actor.user.transaction.retrieve",
1650         argc            => 1,
1651         notes           => <<"  NOTES");
1652         Returns a transaction record
1653         NOTES
1654 sub user_transaction_retrieve {
1655         my( $self, $client, $login_session, $bill_id ) = @_;
1656
1657         # XXX I think I'm deprecated... make sure
1658
1659         my $trans = $apputils->simple_scalar_request( 
1660                 "open-ils.cstore",
1661                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1662                 $bill_id
1663         );
1664
1665         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1666                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1667         return $evt if $evt;
1668         
1669         my $api = $self->api_name();
1670         if($api !~ /fleshed/o) { return $trans; }
1671
1672         if( $trans->xact_type ne 'circulation' ) {
1673                 $logger->debug("Returning non-circ transaction");
1674                 return {transaction => $trans};
1675         }
1676
1677         my $circ = $apputils->simple_scalar_request(
1678                         "open-ils.cstore",
1679                         "open-ils..direct.action.circulation.retrieve",
1680                         $trans->id );
1681
1682         return {transaction => $trans} unless $circ;
1683         $logger->debug("Found the circ transaction");
1684
1685         my $title = $apputils->simple_scalar_request(
1686                 "open-ils.storage", 
1687                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1688                 $circ->target_copy );
1689
1690         return {transaction => $trans, circ => $circ } unless $title;
1691         $logger->debug("Found the circ title");
1692
1693         my $mods;
1694         try {
1695                 my $u = OpenILS::Utils::ModsParser->new();
1696                 $u->start_mods_batch($title->marc());
1697                 $mods = $u->finish_mods_batch();
1698         } otherwise {
1699                 if ($title->id == OILS_PRECAT_RECORD) {
1700                         my $copy = $apputils->simple_scalar_request(
1701                                 "open-ils.cstore",
1702                                 "open-ils.cstore.direct.asset.copy.retrieve",
1703                                 $circ->target_copy );
1704
1705                         $mods = new Fieldmapper::metabib::virtual_record;
1706                         $mods->doc_id(OILS_PRECAT_RECORD);
1707                         $mods->title($copy->dummy_title);
1708                         $mods->author($copy->dummy_author);
1709                 }
1710         };
1711
1712         $logger->debug("MODSized the circ title");
1713
1714         return {transaction => $trans, circ => $circ, record => $mods };
1715 }
1716
1717
1718 __PACKAGE__->register_method(
1719         method  => "hold_request_count",
1720         api_name        => "open-ils.actor.user.hold_requests.count",
1721     authoritative => 1,
1722         argc            => 1,
1723         notes           => <<"  NOTES");
1724         Returns hold ready/total counts
1725         NOTES
1726 sub hold_request_count {
1727         my( $self, $client, $login_session, $userid ) = @_;
1728
1729         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1730                 $login_session, $userid, 'VIEW_HOLD' );
1731         return $evt if $evt;
1732         
1733
1734         my $holds = $apputils->simple_scalar_request(
1735                         "open-ils.cstore",
1736                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1737                         { 
1738                                 usr => $userid,
1739                                 fulfillment_time => {"=" => undef },
1740                                 cancel_time => undef,
1741                         }
1742         );
1743
1744         my @ready;
1745         for my $h (@$holds) {
1746                 next unless $h->capture_time and $h->current_copy;
1747
1748                 my $copy = $apputils->simple_scalar_request(
1749                         "open-ils.cstore",
1750                         "open-ils.cstore.direct.asset.copy.retrieve",
1751                         $h->current_copy
1752                 );
1753
1754                 if ($copy and $copy->status == 8) {
1755                         push @ready, $h;
1756                 }
1757         }
1758
1759         return { total => scalar(@$holds), ready => scalar(@ready) };
1760 }
1761
1762
1763 __PACKAGE__->register_method(
1764         method  => "checkedout_count",
1765         api_name        => "open-ils.actor.user.checked_out.count__",
1766         argc            => 1,
1767         notes           => <<"  NOTES");
1768         Returns a transaction record
1769         NOTES
1770
1771 # XXX Deprecate Me
1772 sub checkedout_count {
1773         my( $self, $client, $login_session, $userid ) = @_;
1774
1775         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1776                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1777         return $evt if $evt;
1778         
1779         my $circs = $apputils->simple_scalar_request(
1780                         "open-ils.cstore",
1781                         "open-ils.cstore.direct.action.circulation.search.atomic",
1782                         { usr => $userid, stop_fines => undef }
1783                         #{ usr => $userid, checkin_time => {"=" => undef } }
1784         );
1785
1786         my $parser = DateTime::Format::ISO8601->new;
1787
1788         my (@out,@overdue);
1789         for my $c (@$circs) {
1790                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1791                 my $due = $due_dt->epoch;
1792
1793                 if ($due < DateTime->today->epoch) {
1794                         push @overdue, $c;
1795                 }
1796         }
1797
1798         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1799 }
1800
1801
1802 __PACKAGE__->register_method(
1803         method          => "checked_out",
1804         api_name                => "open-ils.actor.user.checked_out",
1805     authoritative => 1,
1806         argc                    => 2,
1807         signature       => q/
1808                 Returns a structure of circulations objects sorted by
1809                 out, overdue, lost, claims_returned, long_overdue.
1810                 A list of IDs are returned of each type.
1811                 lost, long_overdue, and claims_returned circ will not
1812                 be "finished" (there is an outstanding balance or some 
1813                 other pending action on the circ). 
1814
1815                 The .count method also includes a 'total' field which 
1816                 sums all "open" circs
1817         /
1818 );
1819
1820 __PACKAGE__->register_method(
1821         method          => "checked_out",
1822         api_name                => "open-ils.actor.user.checked_out.count",
1823     authoritative => 1,
1824         argc                    => 2,
1825         signature       => q/@see open-ils.actor.user.checked_out/
1826 );
1827
1828 sub checked_out {
1829         my( $self, $conn, $auth, $userid ) = @_;
1830
1831         my $e = new_editor(authtoken=>$auth);
1832         return $e->event unless $e->checkauth;
1833
1834         if( $userid ne $e->requestor->id ) {
1835                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1836         }
1837
1838         my $count = $self->api_name =~ /count/;
1839         return _checked_out( $count, $e, $userid );
1840 }
1841
1842 sub _checked_out {
1843         my( $iscount, $e, $userid ) = @_;
1844
1845
1846         my $meth = 'open-ils.storage.actor.user.checked_out';
1847         $meth = "$meth.count" if $iscount;
1848         return $U->storagereq($meth, $userid);
1849
1850 # XXX Old code - moved to storage
1851 #------------------------------------------------------------------------------
1852 #------------------------------------------------------------------------------
1853         my $circs = $e->search_action_circulation( 
1854                 { usr => $userid, checkin_time => undef });
1855
1856         my $parser = DateTime::Format::ISO8601->new;
1857
1858         # split the circs up into overdue and not-overdue circs
1859         my (@out,@overdue);
1860         for my $c (@$circs) {
1861                 if( $c->due_date ) {
1862                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1863                         my $due = $due_dt->epoch;
1864                         if ($due < DateTime->today->epoch) {
1865                                 push @overdue, $c;
1866                         } else {
1867                                 push @out, $c;
1868                         }
1869                 } else {
1870                         push @out, $c;
1871                 }
1872         }
1873
1874         my( @open, @od, @lost, @cr, @lo );
1875
1876         while (my $c = shift(@out)) {
1877                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1878                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1879                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1880                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1881         }
1882
1883         while (my $c = shift(@overdue)) {
1884                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1885                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1886                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1887                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1888         }
1889
1890         if( $iscount ) {
1891                 return {
1892                         total           => @open + @od + @lost + @cr + @lo,
1893                         out             => scalar(@open),
1894                         overdue => scalar(@od),
1895                         lost            => scalar(@lost),
1896                         claims_returned => scalar(@cr),
1897                         long_overdue            => scalar(@lo)
1898                 };
1899         }
1900
1901         return {
1902                 out             => \@open,
1903                 overdue => \@od,
1904                 lost            => \@lost,
1905                 claims_returned => \@cr,
1906                 long_overdue            => \@lo
1907         };
1908 }
1909
1910
1911 sub _checked_out_WHAT {
1912         my( $iscount, $e, $userid ) = @_;
1913
1914         my $circs = $e->search_action_circulation( 
1915                 { usr => $userid, stop_fines => undef });
1916
1917         my $mcircs = $e->search_action_circulation( 
1918                 { 
1919                         usr => $userid, 
1920                         checkin_time => undef, 
1921                         xact_finish => undef, 
1922                 });
1923
1924         
1925         push( @$circs, @$mcircs );
1926
1927         my $parser = DateTime::Format::ISO8601->new;
1928
1929         # split the circs up into overdue and not-overdue circs
1930         my (@out,@overdue);
1931         for my $c (@$circs) {
1932                 if( $c->due_date ) {
1933                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1934                         my $due = $due_dt->epoch;
1935                         if ($due < DateTime->today->epoch) {
1936                                 push @overdue, $c->id;
1937                         } else {
1938                                 push @out, $c->id;
1939                         }
1940                 } else {
1941                         push @out, $c->id;
1942                 }
1943         }
1944
1945         # grab all of the lost, claims-returned, and longoverdue circs
1946         #my $open = $e->search_action_circulation(
1947         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1948
1949
1950         # these items have stop_fines, but no xact_finish, so money
1951         # is owed on them and they have not been checked in
1952         my $open = $e->search_action_circulation(
1953                 {
1954                         usr                             => $userid, 
1955                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1956                         xact_finish             => undef,
1957                         checkin_time    => undef,
1958                 }
1959         );
1960
1961
1962         my( @lost, @cr, @lo );
1963         for my $c (@$open) {
1964                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1965                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1966                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1967         }
1968
1969
1970         if( $iscount ) {
1971                 return {
1972                         total           => @$circs + @lost + @cr + @lo,
1973                         out             => scalar(@out),
1974                         overdue => scalar(@overdue),
1975                         lost            => scalar(@lost),
1976                         claims_returned => scalar(@cr),
1977                         long_overdue            => scalar(@lo)
1978                 };
1979         }
1980
1981         return {
1982                 out             => \@out,
1983                 overdue => \@overdue,
1984                 lost            => \@lost,
1985                 claims_returned => \@cr,
1986                 long_overdue            => \@lo
1987         };
1988 }
1989
1990
1991
1992 __PACKAGE__->register_method(
1993         method          => "checked_in_with_fines",
1994         api_name                => "open-ils.actor.user.checked_in_with_fines",
1995     authoritative => 1,
1996         argc                    => 2,
1997         signature       => q/@see open-ils.actor.user.checked_out/
1998 );
1999 sub checked_in_with_fines {
2000         my( $self, $conn, $auth, $userid ) = @_;
2001
2002         my $e = new_editor(authtoken=>$auth);
2003         return $e->event unless $e->checkauth;
2004
2005         if( $userid ne $e->requestor->id ) {
2006                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
2007         }
2008
2009         # money is owed on these items and they are checked in
2010         my $open = $e->search_action_circulation(
2011                 {
2012                         usr                             => $userid, 
2013                         xact_finish             => undef,
2014                         checkin_time    => { "!=" => undef },
2015                 }
2016         );
2017
2018
2019         my( @lost, @cr, @lo );
2020         for my $c (@$open) {
2021                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2022                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2023                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2024         }
2025
2026         return {
2027                 lost            => \@lost,
2028                 claims_returned => \@cr,
2029                 long_overdue            => \@lo
2030         };
2031 }
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041 __PACKAGE__->register_method(
2042         method  => "user_transaction_history",
2043         api_name        => "open-ils.actor.user.transactions.history",
2044         argc            => 1,
2045         notes           => <<"  NOTES");
2046         Returns a list of billable transaction ids for a user, optionally by type
2047         NOTES
2048 __PACKAGE__->register_method(
2049         method  => "user_transaction_history",
2050         api_name        => "open-ils.actor.user.transactions.history.have_charge",
2051         argc            => 1,
2052         notes           => <<"  NOTES");
2053         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
2054         NOTES
2055 __PACKAGE__->register_method(
2056         method  => "user_transaction_history",
2057         api_name        => "open-ils.actor.user.transactions.history.have_balance",
2058     authoritative => 1,
2059         argc            => 1,
2060         notes           => <<"  NOTES");
2061         Returns a list of billable transaction ids for a user that have a balance, optionally by type
2062         NOTES
2063 __PACKAGE__->register_method(
2064         method  => "user_transaction_history",
2065         api_name        => "open-ils.actor.user.transactions.history.still_open",
2066         argc            => 1,
2067         notes           => <<"  NOTES");
2068         Returns a list of billable transaction ids for a user that are not finished
2069         NOTES
2070 __PACKAGE__->register_method(
2071         method  => "user_transaction_history",
2072         api_name        => "open-ils.actor.user.transactions.history.have_bill",
2073     authoritative => 1,
2074         argc            => 1,
2075         notes           => <<"  NOTES");
2076         Returns a list of billable transaction ids for a user that has billings
2077         NOTES
2078
2079
2080
2081 =head old
2082 sub _user_transaction_history {
2083         my( $self, $client, $login_session, $user_id, $type ) = @_;
2084
2085         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
2086                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
2087         return $evt if $evt;
2088
2089         my $api = $self->api_name();
2090         my @xact;
2091         my @charge;
2092         my @balance;
2093
2094         @xact = (xact_type =>  $type) if(defined($type));
2095         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
2096         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
2097
2098         $logger->debug("searching for transaction history: @xact : @balance, @charge");
2099
2100         my $trans = $apputils->simple_scalar_request( 
2101                 "open-ils.cstore",
2102                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
2103                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
2104
2105         return [ map { $_->id } @$trans ];
2106 }
2107 =cut
2108
2109 =head SEE APPUTILS.PM
2110 sub _make_mbts {
2111         my @xacts = @_;
2112
2113         my @mbts;
2114         for my $x (@xacts) {
2115                 my $s = new Fieldmapper::money::billable_transaction_summary;
2116                 $s->id( $x->id );
2117                 $s->usr( $x->usr );
2118                 $s->xact_start( $x->xact_start );
2119                 $s->xact_finish( $x->xact_finish );
2120
2121                 my $to = 0;
2122                 my $lb = undef;
2123                 for my $b (@{ $x->billings }) {
2124                         next if ($U->is_true($b->voided));
2125                         $to += ($b->amount * 100);
2126                         $lb ||= $b->billing_ts;
2127                         if ($b->billing_ts ge $lb) {
2128                                 $lb = $b->billing_ts;
2129                                 $s->last_billing_note($b->note);
2130                                 $s->last_billing_ts($b->billing_ts);
2131                                 $s->last_billing_type($b->billing_type);
2132                         }
2133                 }
2134
2135                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2136
2137                 my $tp = 0;
2138                 my $lp = undef;
2139                 for my $p (@{ $x->payments }) {
2140                         next if ($U->is_true($p->voided));
2141                         $tp += ($p->amount * 100);
2142                         $lp ||= $p->payment_ts;
2143                         if ($p->payment_ts ge $lp) {
2144                                 $lp = $p->payment_ts;
2145                                 $s->last_payment_note($p->note);
2146                                 $s->last_payment_ts($p->payment_ts);
2147                                 $s->last_payment_type($p->payment_type);
2148                         }
2149                 }
2150                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2151
2152                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2153
2154                 $s->xact_type( 'grocery' ) if ($x->grocery);
2155                 $s->xact_type( 'circulation' ) if ($x->circulation);
2156
2157                 push @mbts, $s;
2158         }
2159
2160         return @mbts;
2161 }
2162 =cut
2163
2164 sub user_transaction_history {
2165         my( $self, $conn, $auth, $userid, $type ) = @_;
2166
2167         # run inside of a transaction to prevent replication delays
2168         my $e = new_editor(xact=>1, authtoken=>$auth);
2169         return $e->die_event unless $e->checkauth;
2170
2171         if( $e->requestor->id ne $userid ) {
2172                 return $e->die_event 
2173                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2174         }
2175
2176         my $api = $self->api_name;
2177         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2178
2179         my @xacts = @{ $e->search_money_billable_transaction(
2180                 [       { usr => $userid, @xact_finish },
2181                         { flesh => 1,
2182                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2183                           order_by => { mbt => 'xact_start DESC' },
2184                         }
2185                 ],
2186       {substream => 1}
2187         ) };
2188
2189         $e->rollback;
2190
2191         #my @mbts = _make_mbts( @xacts );
2192         my @mbts = $U->make_mbts( @xacts );
2193
2194         if(defined($type)) {
2195                 @mbts = grep { $_->xact_type eq $type } @mbts;
2196         }
2197
2198         if($api =~ /have_balance/o) {
2199                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2200         }
2201
2202         if($api =~ /have_charge/o) {
2203                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2204         }
2205
2206         if($api =~ /have_bill/o) {
2207                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2208         }
2209
2210         return [@mbts];
2211 }
2212
2213
2214
2215 __PACKAGE__->register_method(
2216         method  => "user_perms",
2217         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2218         argc            => 1,
2219         notes           => <<"  NOTES");
2220         Returns a list of permissions
2221         NOTES
2222 sub user_perms {
2223         my( $self, $client, $authtoken, $user ) = @_;
2224
2225         my( $staff, $evt ) = $apputils->checkses($authtoken);
2226         return $evt if $evt;
2227
2228         $user ||= $staff->id;
2229
2230         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2231                 return $evt;
2232         }
2233
2234         return $apputils->simple_scalar_request(
2235                 "open-ils.storage",
2236                 "open-ils.storage.permission.user_perms.atomic",
2237                 $user);
2238 }
2239
2240 __PACKAGE__->register_method(
2241         method  => "retrieve_perms",
2242         api_name        => "open-ils.actor.permissions.retrieve",
2243         notes           => <<"  NOTES");
2244         Returns a list of permissions
2245         NOTES
2246 sub retrieve_perms {
2247         my( $self, $client ) = @_;
2248         return $apputils->simple_scalar_request(
2249                 "open-ils.cstore",
2250                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2251                 { id => { '!=' => undef } }
2252         );
2253 }
2254
2255 __PACKAGE__->register_method(
2256         method  => "retrieve_groups",
2257         api_name        => "open-ils.actor.groups.retrieve",
2258         notes           => <<"  NOTES");
2259         Returns a list of user groupss
2260         NOTES
2261 sub retrieve_groups {
2262         my( $self, $client ) = @_;
2263         return new_editor()->retrieve_all_permission_grp_tree();
2264 }
2265
2266 __PACKAGE__->register_method(
2267         method  => "retrieve_org_address",
2268         api_name        => "open-ils.actor.org_unit.address.retrieve",
2269         notes           => <<'  NOTES');
2270         Returns an org_unit address by ID
2271         @param An org_address ID
2272         NOTES
2273 sub retrieve_org_address {
2274         my( $self, $client, $id ) = @_;
2275         return $apputils->simple_scalar_request(
2276                 "open-ils.cstore",
2277                 "open-ils.cstore.direct.actor.org_address.retrieve",
2278                 $id
2279         );
2280 }
2281
2282 __PACKAGE__->register_method(
2283         method  => "retrieve_groups_tree",
2284         api_name        => "open-ils.actor.groups.tree.retrieve",
2285         notes           => <<"  NOTES");
2286         Returns a list of user groups
2287         NOTES
2288 sub retrieve_groups_tree {
2289         my( $self, $client ) = @_;
2290         return new_editor()->search_permission_grp_tree(
2291                 [
2292                         { parent => undef},
2293                         {       
2294                                 flesh                           => -1,
2295                                 flesh_fields    => { pgt => ["children"] }, 
2296                                 order_by                        => { pgt => 'name'}
2297                         }
2298                 ]
2299         )->[0];
2300 }
2301
2302
2303 # turns an org list into an org tree
2304 =head old code
2305 sub build_group_tree {
2306
2307         my( $self, $grplist) = @_;
2308
2309         return $grplist unless ( 
2310                         ref($grplist) and @$grplist > 1 );
2311
2312         my @list = sort { $a->name cmp $b->name } @$grplist;
2313
2314         my $root;
2315         for my $grp (@list) {
2316
2317                 if ($grp and !defined($grp->parent)) {
2318                         $root = $grp;
2319                         next;
2320                 }
2321                 my ($parent) = grep { $_->id == $grp->parent} @list;
2322
2323                 $parent->children([]) unless defined($parent->children); 
2324                 push( @{$parent->children}, $grp );
2325         }
2326
2327         return $root;
2328 }
2329 =cut
2330
2331
2332 __PACKAGE__->register_method(
2333         method  => "add_user_to_groups",
2334         api_name        => "open-ils.actor.user.set_groups",
2335         notes           => <<"  NOTES");
2336         Adds a user to one or more permission groups
2337         NOTES
2338
2339 sub add_user_to_groups {
2340         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2341
2342         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2343                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2344         return $evt if $evt;
2345
2346         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2347                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2348         return $evt if $evt;
2349
2350         $apputils->simplereq(
2351                 'open-ils.storage',
2352                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2353                 
2354         for my $group (@$groups) {
2355                 my $link = Fieldmapper::permission::usr_grp_map->new;
2356                 $link->grp($group);
2357                 $link->usr($userid);
2358
2359                 my $id = $apputils->simplereq(
2360                         'open-ils.storage',
2361                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2362         }
2363
2364         return 1;
2365 }
2366
2367 __PACKAGE__->register_method(
2368         method  => "get_user_perm_groups",
2369         api_name        => "open-ils.actor.user.get_groups",
2370         notes           => <<"  NOTES");
2371         Retrieve a user's permission groups.
2372         NOTES
2373
2374
2375 sub get_user_perm_groups {
2376         my( $self, $client, $authtoken, $userid ) = @_;
2377
2378         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2379                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2380         return $evt if $evt;
2381
2382         return $apputils->simplereq(
2383                 'open-ils.cstore',
2384                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2385 }       
2386
2387
2388 __PACKAGE__->register_method(
2389         method  => "get_user_work_ous",
2390         api_name        => "open-ils.actor.user.get_work_ous",
2391         notes           => <<"  NOTES");
2392         Retrieve a user's work org units.
2393         NOTES
2394 __PACKAGE__->register_method(
2395         method  => "get_user_work_ous",
2396         api_name        => "open-ils.actor.user.get_work_ous.ids",
2397         notes           => <<"  NOTES");
2398         Retrieve a user's work org units.
2399         NOTES
2400
2401
2402 sub get_user_work_ous {
2403         my( $self, $client, $auth, $userid ) = @_;
2404     my $e = new_editor(authtoken=>$auth);
2405     return $e->event unless $e->checkauth;
2406     $userid ||= $e->requestor->id;
2407
2408     if($e->requestor->id != $userid) {
2409         my $user = $e->retrieve_actor_user($userid)
2410             or return $e->event;
2411         return $e->event unless $e->allowed('ASSIGN_WORK_ORG_UNIT', $user->home_ou);
2412     }
2413
2414     return $e->search_permission_usr_work_ou_map({usr => $userid})
2415         unless $self->api_name =~ /.ids$/;
2416
2417     # client just wants a list of org IDs
2418     return $U->get_user_work_ou_ids($e, $userid);
2419 }       
2420
2421
2422
2423
2424 __PACKAGE__->register_method (
2425         method          => 'register_workstation',
2426         api_name                => 'open-ils.actor.workstation.register.override',
2427         signature       => q/@see open-ils.actor.workstation.register/);
2428
2429 __PACKAGE__->register_method (
2430         method          => 'register_workstation',
2431         api_name                => 'open-ils.actor.workstation.register',
2432         signature       => q/
2433                 Registers a new workstion in the system
2434                 @param authtoken The login session key
2435                 @param name The name of the workstation id
2436                 @param owner The org unit that owns this workstation
2437                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2438                 if the name is already in use.
2439         /);
2440
2441 sub register_workstation {
2442         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2443
2444         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2445         return $e->die_event unless $e->checkauth;
2446         return $e->die_event unless $e->allowed('REGISTER_WORKSTATION', $owner);
2447         my $existing = $e->search_actor_workstation({name => $name})->[0];
2448
2449         if( $existing ) {
2450
2451                 if( $self->api_name =~ /override/o ) {
2452             # workstation with the given name exists.  
2453
2454             if($owner ne $existing->owning_lib) {
2455                 # if necessary, update the owning_lib of the workstation
2456
2457                 $logger->info("changing owning lib of workstation ".$existing->id.
2458                     " from ".$existing->owning_lib." to $owner");
2459                             return $e->die_event unless 
2460                     $e->allowed('UPDATE_WORKSTATION', $existing->owning_lib); 
2461
2462                             return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner); 
2463
2464                 $existing->owning_lib($owner);
2465                             return $e->die_event unless $e->update_actor_workstation($existing);
2466
2467                 $e->commit;
2468
2469             } else {
2470                 $logger->info(  
2471                     "attempt to register an existing workstation.  returning existing ID");
2472             }
2473
2474             return $existing->id;
2475
2476                 } else {
2477                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2478                 }
2479         }
2480
2481         my $ws = Fieldmapper::actor::workstation->new;
2482         $ws->owning_lib($owner);
2483         $ws->name($name);
2484         $e->create_actor_workstation($ws) or return $e->die_event;
2485         $e->commit;
2486         return $ws->id; # note: editor sets the id on the new object for us
2487 }
2488
2489 __PACKAGE__->register_method (
2490         method          => 'workstation_list',
2491         api_name                => 'open-ils.actor.workstation.list',
2492         signature       => q/
2493                 Returns a list of workstations registered at the given location
2494                 @param authtoken The login session key
2495                 @param ids A list of org_unit.id's for the workstation owners
2496         /);
2497
2498 sub workstation_list {
2499         my( $self, $conn, $authtoken, @orgs ) = @_;
2500
2501         my $e = new_editor(authtoken=>$authtoken);
2502         return $e->event unless $e->checkauth;
2503     my %results;
2504
2505     for my $o (@orgs) {
2506             return $e->event 
2507             unless $e->allowed('REGISTER_WORKSTATION', $o);
2508         $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2509     }
2510     return \%results;
2511 }
2512
2513
2514
2515
2516
2517
2518
2519 __PACKAGE__->register_method (
2520         method          => 'fetch_patron_note',
2521         api_name                => 'open-ils.actor.note.retrieve.all',
2522     authoritative => 1,
2523         signature       => q/
2524                 Returns a list of notes for a given user
2525                 Requestor must have VIEW_USER permission if pub==false and
2526                 @param authtoken The login session key
2527                 @param args Hash of params including
2528                         patronid : the patron's id
2529                         pub : true if retrieving only public notes
2530         /
2531 );
2532
2533 sub fetch_patron_note {
2534         my( $self, $conn, $authtoken, $args ) = @_;
2535         my $patronid = $$args{patronid};
2536
2537         my($reqr, $evt) = $U->checkses($authtoken);
2538         return $evt if $evt;
2539
2540         my $patron;
2541         ($patron, $evt) = $U->fetch_user($patronid);
2542         return $evt if $evt;
2543
2544         if($$args{pub}) {
2545                 if( $patronid ne $reqr->id ) {
2546                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2547                         return $evt if $evt;
2548                 }
2549                 return $U->cstorereq(
2550                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2551                         { usr => $patronid, pub => 't' } );
2552         }
2553
2554         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2555         return $evt if $evt;
2556
2557         return $U->cstorereq(
2558                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2559 }
2560
2561 __PACKAGE__->register_method (
2562         method          => 'create_user_note',
2563         api_name                => 'open-ils.actor.note.create',
2564         signature       => q/
2565                 Creates a new note for the given user
2566                 @param authtoken The login session key
2567                 @param note The note object
2568         /
2569 );
2570 sub create_user_note {
2571         my( $self, $conn, $authtoken, $note ) = @_;
2572         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2573         return $e->die_event unless $e->checkauth;
2574
2575         my $user = $e->retrieve_actor_user($note->usr)
2576                 or return $e->die_event;
2577
2578         return $e->die_event unless 
2579                 $e->allowed('UPDATE_USER',$user->home_ou);
2580
2581         $note->creator($e->requestor->id);
2582         $e->create_actor_usr_note($note) or return $e->die_event;
2583         $e->commit;
2584         return $note->id;
2585 }
2586
2587
2588 __PACKAGE__->register_method (
2589         method          => 'delete_user_note',
2590         api_name                => 'open-ils.actor.note.delete',
2591         signature       => q/
2592                 Deletes a note for the given user
2593                 @param authtoken The login session key
2594                 @param noteid The note id
2595         /
2596 );
2597 sub delete_user_note {
2598         my( $self, $conn, $authtoken, $noteid ) = @_;
2599
2600         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2601         return $e->die_event unless $e->checkauth;
2602         my $note = $e->retrieve_actor_usr_note($noteid)
2603                 or return $e->die_event;
2604         my $user = $e->retrieve_actor_user($note->usr)
2605                 or return $e->die_event;
2606         return $e->die_event unless 
2607                 $e->allowed('UPDATE_USER', $user->home_ou);
2608         
2609         $e->delete_actor_usr_note($note) or return $e->die_event;
2610         $e->commit;
2611         return 1;
2612 }
2613
2614
2615 __PACKAGE__->register_method (
2616         method          => 'update_user_note',
2617         api_name                => 'open-ils.actor.note.update',
2618         signature       => q/
2619                 @param authtoken The login session key
2620                 @param note The note
2621         /
2622 );
2623
2624 sub update_user_note {
2625         my( $self, $conn, $auth, $note ) = @_;
2626         my $e = new_editor(authtoken=>$auth, xact=>1);
2627         return $e->event unless $e->checkauth;
2628         my $patron = $e->retrieve_actor_user($note->usr)
2629                 or return $e->event;
2630         return $e->event unless 
2631                 $e->allowed('UPDATE_USER', $patron->home_ou);
2632         $e->update_actor_user_note($note)
2633                 or return $e->event;
2634         $e->commit;
2635         return 1;
2636 }
2637
2638
2639
2640
2641 __PACKAGE__->register_method (
2642         method          => 'create_closed_date',
2643         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2644         signature       => q/
2645                 Creates a new closing entry for the given org_unit
2646                 @param authtoken The login session key
2647                 @param note The closed_date object
2648         /
2649 );
2650 sub create_closed_date {
2651         my( $self, $conn, $authtoken, $cd ) = @_;
2652
2653         my( $user, $evt ) = $U->checkses($authtoken);
2654         return $evt if $evt;
2655
2656         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2657         return $evt if $evt;
2658
2659         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2660
2661         my $id = $U->storagereq(
2662                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2663         return $U->DB_UPDATE_FAILED($cd) unless $id;
2664         return $id;
2665 }
2666
2667
2668 __PACKAGE__->register_method (
2669         method          => 'delete_closed_date',
2670         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2671         signature       => q/
2672                 Deletes a closing entry for the given org_unit
2673                 @param authtoken The login session key
2674                 @param noteid The close_date id
2675         /
2676 );
2677 sub delete_closed_date {
2678         my( $self, $conn, $authtoken, $cd ) = @_;
2679
2680         my( $user, $evt ) = $U->checkses($authtoken);
2681         return $evt if $evt;
2682
2683         my $cd_obj;
2684         ($cd_obj, $evt) = fetch_closed_date($cd);
2685         return $evt if $evt;
2686
2687         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2688         return $evt if $evt;
2689
2690         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2691
2692         my $stat = $U->storagereq(
2693                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2694         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2695         return $stat;
2696 }
2697
2698
2699 __PACKAGE__->register_method(
2700         method => 'usrname_exists',
2701         api_name        => 'open-ils.actor.username.exists',
2702         signature => q/
2703                 Returns 1 if the requested username exists, returns 0 otherwise
2704         /
2705 );
2706
2707 sub usrname_exists {
2708         my( $self, $conn, $auth, $usrname ) = @_;
2709         my $e = new_editor(authtoken=>$auth);
2710         return $e->event unless $e->checkauth;
2711         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2712         return $$a[0] if $a and @$a;
2713         return undef;
2714 }
2715
2716 __PACKAGE__->register_method(
2717         method => 'barcode_exists',
2718         api_name        => 'open-ils.actor.barcode.exists',
2719     authoritative => 1,
2720         signature => q/
2721                 Returns 1 if the requested barcode exists, returns 0 otherwise
2722         /
2723 );
2724
2725 sub barcode_exists {
2726         my( $self, $conn, $auth, $barcode ) = @_;
2727         my $e = new_editor(authtoken=>$auth);
2728         return $e->event unless $e->checkauth;
2729         my $card = $e->search_actor_card({barcode => $barcode});
2730     return undef unless @$card;
2731     return $card->[0]->usr;
2732 }
2733
2734
2735 __PACKAGE__->register_method(
2736         method => 'retrieve_net_levels',
2737         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2738 );
2739
2740 sub retrieve_net_levels {
2741         my( $self, $conn, $auth ) = @_;
2742         my $e = new_editor(authtoken=>$auth);
2743         return $e->event unless $e->checkauth;
2744         return $e->retrieve_all_config_net_access_level();
2745 }
2746
2747
2748 __PACKAGE__->register_method(
2749         method => 'fetch_org_by_shortname',
2750         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2751 );
2752 sub fetch_org_by_shortname {
2753         my( $self, $conn, $sname ) = @_;
2754         my $e = new_editor();
2755         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2756         return $e->event unless $org;
2757         return $org;
2758 }
2759
2760
2761 __PACKAGE__->register_method(
2762         method => 'session_home_lib',
2763         api_name => 'open-ils.actor.session.home_lib',
2764 );
2765
2766 sub session_home_lib {
2767         my( $self, $conn, $auth ) = @_;
2768         my $e = new_editor(authtoken=>$auth);
2769         return undef unless $e->checkauth;
2770         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2771         return $org->shortname;
2772 }
2773
2774 __PACKAGE__->register_method(
2775         method => 'session_safe_token',
2776         api_name => 'open-ils.actor.session.safe_token',
2777         signature => q/
2778                 Returns a hashed session ID that is safe for export to the world.
2779                 This safe token will expire after 1 hour of non-use.
2780                 @param auth Active authentication token
2781         /
2782 );
2783
2784 sub session_safe_token {
2785         my( $self, $conn, $auth ) = @_;
2786         my $e = new_editor(authtoken=>$auth);
2787         return undef unless $e->checkauth;
2788
2789         my $safe_token = md5_hex($auth);
2790
2791         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2792
2793         # Add more like the following if needed...
2794         $cache->put_cache(
2795                 "safe-token-home_lib-shortname-$safe_token",
2796                 $e->retrieve_actor_org_unit(
2797                         $e->requestor->home_ou
2798                 )->shortname,
2799                 60 * 60
2800         );
2801
2802         return $safe_token;
2803 }
2804
2805
2806 __PACKAGE__->register_method(
2807         method => 'safe_token_home_lib',
2808         api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2809         signature => q/
2810                 Returns the home library shortname from the session
2811                 asscociated with a safe token from generated by
2812                 open-ils.actor.session.safe_token.
2813                 @param safe_token Active safe token
2814         /
2815 );
2816
2817 sub safe_token_home_lib {
2818         my( $self, $conn, $safe_token ) = @_;
2819
2820         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2821         return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2822 }
2823
2824
2825
2826 __PACKAGE__->register_method(
2827         method => 'slim_tree',
2828         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2829 );
2830 sub slim_tree {
2831         my $tree = new_editor()->search_actor_org_unit( 
2832                 [
2833                         {"parent_ou" => undef },
2834                         {
2835                                 flesh                           => -1,
2836                                 flesh_fields    => { aou =>  ['children'] },
2837                                 order_by                        => { aou => 'name'},
2838                                 select                  => { aou => ["id","shortname", "name"]},
2839                         }
2840                 ]
2841         )->[0];
2842
2843         return trim_tree($tree);
2844 }
2845
2846
2847 sub trim_tree {
2848         my $tree = shift;
2849         return undef unless $tree;
2850         my $htree = {
2851                 code => $tree->shortname,
2852                 name => $tree->name,
2853         };
2854         if( $tree->children and @{$tree->children} ) {
2855                 $htree->{children} = [];
2856                 for my $c (@{$tree->children}) {
2857                         push( @{$htree->{children}}, trim_tree($c) );
2858                 }
2859         }
2860
2861         return $htree;
2862 }
2863
2864
2865 __PACKAGE__->register_method(
2866         method  => "update_penalties",
2867         api_name        => "open-ils.actor.user.penalties.update");
2868 sub update_penalties {
2869         my( $self, $conn, $auth, $userid ) = @_;
2870         my $e = new_editor(authtoken=>$auth);
2871         return $e->event unless $e->checkauth;
2872         $U->update_patron_penalties( 
2873                 authtoken => $auth,
2874                 patronid  => $userid,
2875         );
2876         return 1;
2877 }
2878
2879
2880
2881 __PACKAGE__->register_method(
2882         method  => "user_retrieve_fleshed_by_id",
2883         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2884
2885 sub user_retrieve_fleshed_by_id {
2886         my( $self, $client, $auth, $user_id, $fields ) = @_;
2887         my $e = new_editor(authtoken => $auth);
2888         return $e->event unless $e->checkauth;
2889
2890         if( $e->requestor->id != $user_id ) {
2891                 return $e->event unless $e->allowed('VIEW_USER');
2892         }
2893
2894         $fields ||= [
2895                 "cards",
2896                 "card",
2897                 "standing_penalties",
2898                 "addresses",
2899                 "billing_address",
2900                 "mailing_address",
2901                 "stat_cat_entries" ];
2902         return new_flesh_user($user_id, $fields, $e);
2903 }
2904
2905
2906 sub new_flesh_user {
2907
2908         my $id = shift;
2909         my $fields = shift || [];
2910         my $e   = shift || new_editor(xact=>1);
2911
2912         my $user = $e->retrieve_actor_user(
2913         [
2914         $id,
2915         {
2916                 "flesh"                         => 1,
2917                 "flesh_fields" =>  { "au" => $fields }
2918         }
2919         ]
2920         ) or return $e->event;
2921
2922
2923         if( grep { $_ eq 'addresses' } @$fields ) {
2924
2925                 $user->addresses([]) unless @{$user->addresses};
2926         
2927                 if( ref $user->billing_address ) {
2928                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2929                                 push( @{$user->addresses}, $user->billing_address );
2930                         }
2931                 }
2932         
2933                 if( ref $user->mailing_address ) {
2934                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2935                                 push( @{$user->addresses}, $user->mailing_address );
2936                         }
2937                 }
2938         }
2939
2940         $e->rollback;
2941         $user->clear_passwd();
2942         return $user;
2943 }
2944
2945
2946
2947
2948 __PACKAGE__->register_method(
2949         method  => "user_retrieve_parts",
2950         api_name        => "open-ils.actor.user.retrieve.parts",);
2951
2952 sub user_retrieve_parts {
2953         my( $self, $client, $auth, $user_id, $fields ) = @_;
2954         my $e = new_editor(authtoken => $auth);
2955         return $e->event unless $e->checkauth;
2956         if( $e->requestor->id != $user_id ) {
2957                 return $e->event unless $e->allowed('VIEW_USER');
2958         }
2959         my @resp;
2960         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2961         push(@resp, $user->$_()) for(@$fields);
2962         return \@resp;
2963 }
2964
2965
2966
2967 __PACKAGE__->register_method(
2968     method => 'user_opt_in_enabled',
2969     api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2970     signature => q/
2971         @return 1 if user opt-in is globally enabled, 0 otherwise.
2972     /);
2973
2974 sub user_opt_in_enabled {
2975     my($self, $conn) = @_;
2976     my $sc = OpenSRF::Utils::SettingsClient->new;
2977     return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true'; 
2978     return 0;
2979 }
2980     
2981
2982 __PACKAGE__->register_method(
2983     method => 'user_opt_in_at_org',
2984     api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2985     signature => q/
2986         @param $auth The auth token
2987         @param user_id The ID of the user to test
2988         @return 1 if the user has opted in at the specified org,
2989             event on error, and 0 otherwise. /);
2990 sub user_opt_in_at_org {
2991     my($self, $conn, $auth, $user_id) = @_;
2992
2993     # see if we even need to enforce the opt-in value
2994     return 1 unless user_opt_in_enabled($self);
2995
2996         my $e = new_editor(authtoken => $auth);
2997         return $e->event unless $e->checkauth;
2998     my $org_id = $e->requestor->ws_ou;
2999
3000     my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3001         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3002
3003     # user is automatically opted-in at the home org
3004     return 1 if $user->home_ou eq $org_id;
3005
3006     my $vals = $e->search_actor_usr_org_unit_opt_in(
3007         {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3008
3009     return 1 if @$vals;
3010     return 0;
3011 }
3012
3013 __PACKAGE__->register_method(
3014     method => 'create_user_opt_in_at_org',
3015     api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3016     signature => q/
3017         @param $auth The auth token
3018         @param user_id The ID of the user to test
3019         @return The ID of the newly created object, event on error./);
3020
3021 sub create_user_opt_in_at_org {
3022     my($self, $conn, $auth, $user_id) = @_;
3023
3024         my $e = new_editor(authtoken => $auth, xact=>1);
3025         return $e->die_event unless $e->checkauth;
3026     my $org_id = $e->requestor->ws_ou;
3027
3028     my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3029         return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3030
3031     my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3032
3033     $opt_in->org_unit($org_id);
3034     $opt_in->usr($user_id);
3035     $opt_in->staff($e->requestor->id);
3036     $opt_in->opt_in_ts('now');
3037     $opt_in->opt_in_ws($e->requestor->wsid);
3038
3039     $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3040         or return $e->die_event;
3041
3042     $e->commit;
3043
3044     return $opt_in->id;
3045 }
3046
3047
3048 1;
3049