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