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