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