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