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