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