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