]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
new checked_out
[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->id;
1683                         } else {
1684                                 push @out, $c->id;
1685                         }
1686                 } else {
1687                         push @out, $c->id;
1688                 }
1689         }
1690
1691         my( @lost, @cr, @lo );
1692         for my $c (@$open) {
1693                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1694                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1695                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1696         }
1697
1698
1699         if( $iscount ) {
1700                 return {
1701                         total           => @$circs + @lost + @cr + @lo,
1702                         out             => scalar(@out),
1703                         overdue => scalar(@overdue),
1704                         lost            => scalar(@lost),
1705                         claims_returned => scalar(@cr),
1706                         long_overdue            => scalar(@lo)
1707                 };
1708         }
1709
1710         return {
1711                 out             => \@out,
1712                 overdue => \@overdue,
1713                 lost            => \@lost,
1714                 claims_returned => \@cr,
1715                 long_overdue            => \@lo
1716         };
1717 }
1718
1719
1720 sub _checked_out_WHAT {
1721         my( $iscount, $e, $userid ) = @_;
1722
1723         my $circs = $e->search_action_circulation( 
1724                 { usr => $userid, stop_fines => undef });
1725
1726         my $mcircs = $e->search_action_circulation( 
1727                 { 
1728                         usr => $userid, 
1729                         checkin_time => undef, 
1730                         xact_finish => undef, 
1731                 });
1732
1733         
1734         push( @$circs, @$mcircs );
1735
1736         my $parser = DateTime::Format::ISO8601->new;
1737
1738         # split the circs up into overdue and not-overdue circs
1739         my (@out,@overdue);
1740         for my $c (@$circs) {
1741                 if( $c->due_date ) {
1742                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1743                         my $due = $due_dt->epoch;
1744                         if ($due < DateTime->today->epoch) {
1745                                 push @overdue, $c->id;
1746                         } else {
1747                                 push @out, $c->id;
1748                         }
1749                 } else {
1750                         push @out, $c->id;
1751                 }
1752         }
1753
1754         # grab all of the lost, claims-returned, and longoverdue circs
1755         #my $open = $e->search_action_circulation(
1756         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1757
1758
1759         # these items have stop_fines, but no xact_finish, so money
1760         # is owed on them and they have not been checked in
1761         my $open = $e->search_action_circulation(
1762                 {
1763                         usr                             => $userid, 
1764                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1765                         xact_finish             => undef,
1766                         checkin_time    => undef,
1767                 }
1768         );
1769
1770
1771         my( @lost, @cr, @lo );
1772         for my $c (@$open) {
1773                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1774                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1775                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1776         }
1777
1778
1779         if( $iscount ) {
1780                 return {
1781                         total           => @$circs + @lost + @cr + @lo,
1782                         out             => scalar(@out),
1783                         overdue => scalar(@overdue),
1784                         lost            => scalar(@lost),
1785                         claims_returned => scalar(@cr),
1786                         long_overdue            => scalar(@lo)
1787                 };
1788         }
1789
1790         return {
1791                 out             => \@out,
1792                 overdue => \@overdue,
1793                 lost            => \@lost,
1794                 claims_returned => \@cr,
1795                 long_overdue            => \@lo
1796         };
1797 }
1798
1799
1800
1801 __PACKAGE__->register_method(
1802         method          => "checked_in_with_fines",
1803         api_name                => "open-ils.actor.user.checked_in_with_fines",
1804         argc                    => 2,
1805         signature       => q/@see open-ils.actor.user.checked_out/
1806 );
1807 sub checked_in_with_fines {
1808         my( $self, $conn, $auth, $userid ) = @_;
1809
1810         my $e = new_editor(authtoken=>$auth);
1811         return $e->event unless $e->checkauth;
1812
1813         if( $userid ne $e->requestor->id ) {
1814                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1815         }
1816
1817         # money is owed on these items and they are checked in
1818         my $open = $e->search_action_circulation(
1819                 {
1820                         usr                             => $userid, 
1821                         xact_finish             => undef,
1822                         checkin_time    => { "!=" => undef },
1823                 }
1824         );
1825
1826
1827         my( @lost, @cr, @lo );
1828         for my $c (@$open) {
1829                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1830                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1831                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1832         }
1833
1834         return {
1835                 lost            => \@lost,
1836                 claims_returned => \@cr,
1837                 long_overdue            => \@lo
1838         };
1839 }
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849 __PACKAGE__->register_method(
1850         method  => "user_transaction_history",
1851         api_name        => "open-ils.actor.user.transactions.history",
1852         argc            => 1,
1853         notes           => <<"  NOTES");
1854         Returns a list of billable transaction ids for a user, optionally by type
1855         NOTES
1856 __PACKAGE__->register_method(
1857         method  => "user_transaction_history",
1858         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1859         argc            => 1,
1860         notes           => <<"  NOTES");
1861         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1862         NOTES
1863 __PACKAGE__->register_method(
1864         method  => "user_transaction_history",
1865         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1866         argc            => 1,
1867         notes           => <<"  NOTES");
1868         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1869         NOTES
1870 __PACKAGE__->register_method(
1871         method  => "user_transaction_history",
1872         api_name        => "open-ils.actor.user.transactions.history.still_open",
1873         argc            => 1,
1874         notes           => <<"  NOTES");
1875         Returns a list of billable transaction ids for a user that are not finished
1876         NOTES
1877 __PACKAGE__->register_method(
1878         method  => "user_transaction_history",
1879         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1880         argc            => 1,
1881         notes           => <<"  NOTES");
1882         Returns a list of billable transaction ids for a user that has billings
1883         NOTES
1884
1885
1886
1887 =head old
1888 sub _user_transaction_history {
1889         my( $self, $client, $login_session, $user_id, $type ) = @_;
1890
1891         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1892                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1893         return $evt if $evt;
1894
1895         my $api = $self->api_name();
1896         my @xact;
1897         my @charge;
1898         my @balance;
1899
1900         @xact = (xact_type =>  $type) if(defined($type));
1901         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
1902         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
1903
1904         $logger->debug("searching for transaction history: @xact : @balance, @charge");
1905
1906         my $trans = $apputils->simple_scalar_request( 
1907                 "open-ils.cstore",
1908                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
1909                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
1910
1911         return [ map { $_->id } @$trans ];
1912 }
1913 =cut
1914
1915 sub _make_mbts {
1916         my @xacts = @_;
1917
1918         my @mbts;
1919         for my $x (@xacts) {
1920                 my $s = new Fieldmapper::money::billable_transaction_summary;
1921                 $s->id( $x->id );
1922                 $s->usr( $x->usr );
1923                 $s->xact_start( $x->xact_start );
1924                 $s->xact_finish( $x->xact_finish );
1925
1926                 my $to = 0;
1927                 my $lb = undef;
1928                 for my $b (@{ $x->billings }) {
1929                         next if ($U->is_true($b->voided));
1930                         $to += int($b->amount * 100);
1931                         $lb ||= $b->billing_ts;
1932                         if ($b->billing_ts ge $lb) {
1933                                 $lb = $b->billing_ts;
1934                                 $s->last_billing_note($b->note);
1935                                 $s->last_billing_ts($b->billing_ts);
1936                                 $s->last_billing_type($b->billing_type);
1937                         }
1938                 }
1939
1940                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
1941
1942                 my $tp = 0;
1943                 my $lp = undef;
1944                 for my $p (@{ $x->payments }) {
1945                         next if ($U->is_true($p->voided));
1946                         $tp += int($p->amount * 100);
1947                         $lp ||= $p->payment_ts;
1948                         if ($p->payment_ts ge $lp) {
1949                                 $lp = $p->payment_ts;
1950                                 $s->last_payment_note($p->note);
1951                                 $s->last_payment_ts($p->payment_ts);
1952                                 $s->last_payment_type($p->payment_type);
1953                         }
1954                 }
1955                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
1956
1957                 $s->balance_owed( sprintf('%0.2f', int($to - $tp) / 100) );
1958
1959                 $s->xact_type( 'grocery' ) if ($x->grocery);
1960                 $s->xact_type( 'circulation' ) if ($x->circulation);
1961
1962                 push @mbts, $s;
1963         }
1964
1965         return @mbts;
1966 }
1967
1968 sub user_transaction_history {
1969         my( $self, $conn, $auth, $userid, $type ) = @_;
1970         my $e = new_editor(authtoken=>$auth);
1971         return $e->event unless $e->checkauth;
1972         return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS');
1973
1974         my $api = $self->api_name;
1975         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
1976
1977         my @xacts = @{ $e->search_money_billable_transaction(
1978                 [       { usr => $userid, @xact_finish },
1979                         { flesh => 1,
1980                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
1981                           order_by => { mbt => 'xact_start DESC' },
1982                         }
1983                 ]
1984         ) };
1985
1986         my @mbts = _make_mbts( @xacts );
1987
1988         if(defined($type)) {
1989                 @mbts = grep { $_->xact_type eq $type } @mbts;
1990         }
1991
1992         if($api =~ /have_balance/o) {
1993                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
1994         }
1995
1996         if($api =~ /have_charge/o) {
1997                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
1998         }
1999
2000         if($api =~ /have_bill/o) {
2001                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2002         }
2003
2004         return [@mbts];
2005 }
2006
2007
2008
2009 __PACKAGE__->register_method(
2010         method  => "user_perms",
2011         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2012         argc            => 1,
2013         notes           => <<"  NOTES");
2014         Returns a list of permissions
2015         NOTES
2016 sub user_perms {
2017         my( $self, $client, $authtoken, $user ) = @_;
2018
2019         my( $staff, $evt ) = $apputils->checkses($authtoken);
2020         return $evt if $evt;
2021
2022         $user ||= $staff->id;
2023
2024         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2025                 return $evt;
2026         }
2027
2028         return $apputils->simple_scalar_request(
2029                 "open-ils.storage",
2030                 "open-ils.storage.permission.user_perms.atomic",
2031                 $user);
2032 }
2033
2034 __PACKAGE__->register_method(
2035         method  => "retrieve_perms",
2036         api_name        => "open-ils.actor.permissions.retrieve",
2037         notes           => <<"  NOTES");
2038         Returns a list of permissions
2039         NOTES
2040 sub retrieve_perms {
2041         my( $self, $client ) = @_;
2042         return $apputils->simple_scalar_request(
2043                 "open-ils.cstore",
2044                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2045                 { id => { '!=' => undef } }
2046         );
2047 }
2048
2049 __PACKAGE__->register_method(
2050         method  => "retrieve_groups",
2051         api_name        => "open-ils.actor.groups.retrieve",
2052         notes           => <<"  NOTES");
2053         Returns a list of user groupss
2054         NOTES
2055 sub retrieve_groups {
2056         my( $self, $client ) = @_;
2057         return new_editor()->retrieve_all_permission_grp_tree();
2058 }
2059
2060 __PACKAGE__->register_method(
2061         method  => "retrieve_org_address",
2062         api_name        => "open-ils.actor.org_unit.address.retrieve",
2063         notes           => <<'  NOTES');
2064         Returns an org_unit address by ID
2065         @param An org_address ID
2066         NOTES
2067 sub retrieve_org_address {
2068         my( $self, $client, $id ) = @_;
2069         return $apputils->simple_scalar_request(
2070                 "open-ils.cstore",
2071                 "open-ils.cstore.direct.actor.org_address.retrieve",
2072                 $id
2073         );
2074 }
2075
2076 __PACKAGE__->register_method(
2077         method  => "retrieve_groups_tree",
2078         api_name        => "open-ils.actor.groups.tree.retrieve",
2079         notes           => <<"  NOTES");
2080         Returns a list of user groups
2081         NOTES
2082 sub retrieve_groups_tree {
2083         my( $self, $client ) = @_;
2084         return new_editor()->search_permission_grp_tree(
2085                 [
2086                         { parent => undef},
2087                         {       
2088                                 flesh                           => 10, 
2089                                 flesh_fields    => { pgt => ["children"] }, 
2090                                 order_by                        => { pgt => 'name'}
2091                         }
2092                 ]
2093         )->[0];
2094 }
2095
2096
2097 # turns an org list into an org tree
2098 =head old code
2099 sub build_group_tree {
2100
2101         my( $self, $grplist) = @_;
2102
2103         return $grplist unless ( 
2104                         ref($grplist) and @$grplist > 1 );
2105
2106         my @list = sort { $a->name cmp $b->name } @$grplist;
2107
2108         my $root;
2109         for my $grp (@list) {
2110
2111                 if ($grp and !defined($grp->parent)) {
2112                         $root = $grp;
2113                         next;
2114                 }
2115                 my ($parent) = grep { $_->id == $grp->parent} @list;
2116
2117                 $parent->children([]) unless defined($parent->children); 
2118                 push( @{$parent->children}, $grp );
2119         }
2120
2121         return $root;
2122 }
2123 =cut
2124
2125
2126 __PACKAGE__->register_method(
2127         method  => "add_user_to_groups",
2128         api_name        => "open-ils.actor.user.set_groups",
2129         notes           => <<"  NOTES");
2130         Adds a user to one or more permission groups
2131         NOTES
2132
2133 sub add_user_to_groups {
2134         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2135
2136         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2137                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2138         return $evt if $evt;
2139
2140         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2141                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2142         return $evt if $evt;
2143
2144         $apputils->simplereq(
2145                 'open-ils.storage',
2146                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2147                 
2148         for my $group (@$groups) {
2149                 my $link = Fieldmapper::permission::usr_grp_map->new;
2150                 $link->grp($group);
2151                 $link->usr($userid);
2152
2153                 my $id = $apputils->simplereq(
2154                         'open-ils.storage',
2155                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2156         }
2157
2158         return 1;
2159 }
2160
2161 __PACKAGE__->register_method(
2162         method  => "get_user_perm_groups",
2163         api_name        => "open-ils.actor.user.get_groups",
2164         notes           => <<"  NOTES");
2165         Retrieve a user's permission groups.
2166         NOTES
2167
2168
2169 sub get_user_perm_groups {
2170         my( $self, $client, $authtoken, $userid ) = @_;
2171
2172         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2173                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2174         return $evt if $evt;
2175
2176         return $apputils->simplereq(
2177                 'open-ils.cstore',
2178                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2179 }       
2180
2181
2182
2183 __PACKAGE__->register_method (
2184         method          => 'register_workstation',
2185         api_name                => 'open-ils.actor.workstation.register.override',
2186         signature       => q/@see open-ils.actor.workstation.register/);
2187
2188 __PACKAGE__->register_method (
2189         method          => 'register_workstation',
2190         api_name                => 'open-ils.actor.workstation.register',
2191         signature       => q/
2192                 Registers a new workstion in the system
2193                 @param authtoken The login session key
2194                 @param name The name of the workstation id
2195                 @param owner The org unit that owns this workstation
2196                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2197                 if the name is already in use.
2198         /);
2199
2200 sub _register_workstation {
2201         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2202         my( $requestor, $evt ) = $U->checkses($authtoken);
2203         return $evt if $evt;
2204         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2205         return $evt if $evt;
2206
2207         my $ws = $U->cstorereq(
2208                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2209         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2210
2211         $ws = Fieldmapper::actor::workstation->new;
2212         $ws->owning_lib($owner);
2213         $ws->name($name);
2214
2215         my $id = $U->storagereq(
2216                 'open-ils.storage.direct.actor.workstation.create', $ws );
2217         return $U->DB_UPDATE_FAILED($ws) unless $id;
2218
2219         $ws->id($id);
2220         return $ws->id();
2221 }
2222
2223 sub register_workstation {
2224         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2225
2226         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2227         return $e->event unless $e->checkauth;
2228         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2229         my $existing = $e->search_actor_workstation({name => $name});
2230
2231         if( @$existing ) {
2232                 if( $self->api_name =~ /override/o ) {
2233                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2234                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2235                 } else {
2236                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2237                 }
2238         }
2239
2240         my $ws = Fieldmapper::actor::workstation->new;
2241         $ws->owning_lib($owner);
2242         $ws->name($name);
2243         $e->create_actor_workstation($ws) or return $e->event;
2244         $e->commit;
2245         return $ws->id; # note: editor sets the id on the new object for us
2246 }
2247
2248
2249 __PACKAGE__->register_method (
2250         method          => 'fetch_patron_note',
2251         api_name                => 'open-ils.actor.note.retrieve.all',
2252         signature       => q/
2253                 Returns a list of notes for a given user
2254                 Requestor must have VIEW_USER permission if pub==false and
2255                 @param authtoken The login session key
2256                 @param args Hash of params including
2257                         patronid : the patron's id
2258                         pub : true if retrieving only public notes
2259         /
2260 );
2261
2262 sub fetch_patron_note {
2263         my( $self, $conn, $authtoken, $args ) = @_;
2264         my $patronid = $$args{patronid};
2265
2266         my($reqr, $evt) = $U->checkses($authtoken);
2267
2268         my $patron;
2269         ($patron, $evt) = $U->fetch_user($patronid);
2270         return $evt if $evt;
2271
2272         if($$args{pub}) {
2273                 if( $patronid ne $reqr->id ) {
2274                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2275                         return $evt if $evt;
2276                 }
2277                 return $U->cstorereq(
2278                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2279                         { usr => $patronid, pub => 't' } );
2280         }
2281
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', { usr => $patronid } );
2287 }
2288
2289 __PACKAGE__->register_method (
2290         method          => 'create_user_note',
2291         api_name                => 'open-ils.actor.note.create',
2292         signature       => q/
2293                 Creates a new note for the given user
2294                 @param authtoken The login session key
2295                 @param note The note object
2296         /
2297 );
2298 sub create_user_note {
2299         my( $self, $conn, $authtoken, $note ) = @_;
2300         my( $reqr, $patron, $evt ) = 
2301                 $U->checkses_requestor($authtoken, $note->usr, 'UPDATE_USER');
2302         return $evt if $evt;
2303         $logger->activity("user ".$reqr->id." creating note for user ".$note->usr);
2304
2305         $note->creator($reqr->id);
2306         my $id = $U->storagereq(
2307                 'open-ils.storage.direct.actor.usr_note.create', $note );
2308         return $U->DB_UPDATE_FAILED($note) unless $id;
2309         return $id;
2310 }
2311
2312
2313 __PACKAGE__->register_method (
2314         method          => 'delete_user_note',
2315         api_name                => 'open-ils.actor.note.delete',
2316         signature       => q/
2317                 Deletes a note for the given user
2318                 @param authtoken The login session key
2319                 @param noteid The note id
2320         /
2321 );
2322 sub delete_user_note {
2323         my( $self, $conn, $authtoken, $noteid ) = @_;
2324
2325         my $note = $U->cstorereq(
2326                 'open-ils.cstore.direct.actor.usr_note.retrieve', $noteid);
2327         return OpenILS::Event->new('ACTOR_USER_NOTE_NOT_FOUND') unless $note;
2328
2329         my( $reqr, $patron, $evt ) = 
2330                 $U->checkses_requestor($authtoken, $note->usr, 'UPDATE_USER');
2331         return $evt if $evt;
2332         $logger->activity("user ".$reqr->id." deleting note [$noteid] for user ".$note->usr);
2333
2334         my $stat = $U->storagereq(
2335                 'open-ils.storage.direct.actor.usr_note.delete', $noteid );
2336         return $U->DB_UPDATE_FAILED($note) unless defined $stat;
2337         return $stat;
2338 }
2339
2340
2341 __PACKAGE__->register_method (
2342         method          => 'update_user_note',
2343         api_name                => 'open-ils.actor.note.update',
2344         signature       => q/
2345                 @param authtoken The login session key
2346                 @param note The note
2347         /
2348 );
2349
2350 sub update_user_note {
2351         my( $self, $conn, $auth, $note ) = @_;
2352         my $e = new_editor(authtoken=>$auth, xact=>1);
2353         return $e->event unless $e->checkauth;
2354         my $patron = $e->retrieve_actor_user($note->usr)
2355                 or return $e->event;
2356         return $e->event unless 
2357                 $e->allowed('UPDATE_USER', $patron->home_ou);
2358         $e->update_actor_user_note($note)
2359                 or return $e->event;
2360         $e->commit;
2361         return 1;
2362 }
2363
2364
2365
2366
2367 __PACKAGE__->register_method (
2368         method          => 'create_closed_date',
2369         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2370         signature       => q/
2371                 Creates a new closing entry for the given org_unit
2372                 @param authtoken The login session key
2373                 @param note The closed_date object
2374         /
2375 );
2376 sub create_closed_date {
2377         my( $self, $conn, $authtoken, $cd ) = @_;
2378
2379         my( $user, $evt ) = $U->checkses($authtoken);
2380         return $evt if $evt;
2381
2382         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2383         return $evt if $evt;
2384
2385         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2386
2387         my $id = $U->storagereq(
2388                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2389         return $U->DB_UPDATE_FAILED($cd) unless $id;
2390         return $id;
2391 }
2392
2393
2394 __PACKAGE__->register_method (
2395         method          => 'delete_closed_date',
2396         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2397         signature       => q/
2398                 Deletes a closing entry for the given org_unit
2399                 @param authtoken The login session key
2400                 @param noteid The close_date id
2401         /
2402 );
2403 sub delete_closed_date {
2404         my( $self, $conn, $authtoken, $cd ) = @_;
2405
2406         my( $user, $evt ) = $U->checkses($authtoken);
2407         return $evt if $evt;
2408
2409         my $cd_obj;
2410         ($cd_obj, $evt) = fetch_closed_date($cd);
2411         return $evt if $evt;
2412
2413         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2414         return $evt if $evt;
2415
2416         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2417
2418         my $stat = $U->storagereq(
2419                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2420         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2421         return $stat;
2422 }
2423
2424
2425 __PACKAGE__->register_method(
2426         method => 'usrname_exists',
2427         api_name        => 'open-ils.actor.username.exists',
2428         signature => q/
2429                 Returns 1 if the requested username exists, returns 0 otherwise
2430         /
2431 );
2432
2433 sub usrname_exists {
2434         my( $self, $conn, $auth, $usrname ) = @_;
2435         my $e = new_editor(authtoken=>$auth);
2436         return $e->event unless $e->checkauth;
2437         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2438         return $$a[0] if $a and @$a;
2439         return 0;
2440 }
2441
2442 __PACKAGE__->register_method(
2443         method => 'barcode_exists',
2444         api_name        => 'open-ils.actor.barcode.exists',
2445         signature => q/
2446                 Returns 1 if the requested barcode exists, returns 0 otherwise
2447         /
2448 );
2449
2450 sub barcode_exists {
2451         my( $self, $conn, $auth, $barcode ) = @_;
2452         my $e = new_editor(authtoken=>$auth);
2453         return $e->event unless $e->checkauth;
2454         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2455         return $$a[0] if $a and @$a;
2456         return 0;
2457 }
2458
2459
2460 __PACKAGE__->register_method(
2461         method => 'retrieve_net_levels',
2462         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2463 );
2464
2465 sub retrieve_net_levels {
2466         my( $self, $conn, $auth ) = @_;
2467         my $e = new_editor(authtoken=>$auth);
2468         return $e->event unless $e->checkauth;
2469         return $e->retrieve_all_config_net_access_level();
2470 }
2471
2472
2473 __PACKAGE__->register_method(
2474         method => 'fetch_org_by_shortname',
2475         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2476 );
2477 sub fetch_org_by_shortname {
2478         my( $self, $conn, $sname ) = @_;
2479         my $e = new_editor();
2480         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2481         return $e->event unless $org;
2482         return $org;
2483 }
2484
2485
2486 __PACKAGE__->register_method(
2487         method => 'session_home_lib',
2488         api_name => 'open-ils.actor.session.home_lib',
2489 );
2490
2491 sub session_home_lib {
2492         my( $self, $conn, $auth ) = @_;
2493         my $e = new_editor(authtoken=>$auth);
2494         return undef unless $e->checkauth;
2495         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2496         return $org->shortname;
2497 }
2498
2499
2500
2501 __PACKAGE__->register_method(
2502         method => 'slim_tree',
2503         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2504 );
2505 sub slim_tree {
2506         my $tree = new_editor()->search_actor_org_unit( 
2507                 [
2508                         {"parent_ou" => undef },
2509                         {
2510                                 flesh                           => 2,
2511                                 flesh_fields    => { aou =>  ['children'] },
2512                                 order_by                        => { aou => 'name'},
2513                                 select                  => { aou => ["id","shortname", "name"]},
2514                         }
2515                 ]
2516         )->[0];
2517
2518         return trim_tree($tree);
2519 }
2520
2521
2522 sub trim_tree {
2523         my $tree = shift;
2524         return undef unless $tree;
2525         my $htree = {
2526                 code => $tree->shortname,
2527                 name => $tree->name,
2528         };
2529         if( $tree->children and @{$tree->children} ) {
2530                 $htree->{children} = [];
2531                 for my $c (@{$tree->children}) {
2532                         push( @{$htree->{children}}, trim_tree($c) );
2533                 }
2534         }
2535
2536         return $htree;
2537 }
2538
2539
2540
2541 __PACKAGE__->register_method(
2542         method  => "user_retrieve_fleshed_by_id",
2543         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2544
2545 sub user_retrieve_fleshed_by_id {
2546         my( $self, $client, $auth, $user_id, $fields ) = @_;
2547         my $e = new_editor(authtoken => $auth);
2548         return $e->event unless $e->checkauth;
2549         if( $e->requestor->id != $user_id ) {
2550                 return $e->event unless $e->allowed('VIEW_USER');
2551         }
2552         $fields ||= [
2553                 "cards",
2554                 "card",
2555                 "standing_penalties",
2556                 "addresses",
2557                 "billing_address",
2558                 "mailing_address",
2559                 "stat_cat_entries" ];
2560         return new_flesh_user($user_id, $fields, $e);
2561 }
2562
2563
2564 sub new_flesh_user {
2565
2566         my $id = shift;
2567         my $fields = shift || [];
2568         my $e   = shift || new_editor(xact=>1);
2569
2570         my $user = $e->retrieve_actor_user(
2571         [
2572         $id,
2573         {
2574                 "flesh"                         => 1,
2575                 "flesh_fields" =>  { "au" => $fields }
2576         }
2577         ]
2578         ) or return $e->event;
2579
2580
2581         if( grep { $_ eq 'addresses' } @$fields ) {
2582
2583                 $user->addresses([]) unless @{$user->addresses};
2584         
2585                 if( ref $user->billing_address ) {
2586                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2587                                 push( @{$user->addresses}, $user->billing_address );
2588                         }
2589                 }
2590         
2591                 if( ref $user->mailing_address ) {
2592                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2593                                 push( @{$user->addresses}, $user->mailing_address );
2594                         }
2595                 }
2596         }
2597
2598         $e->disconnect;
2599         $user->clear_passwd();
2600         return $user;
2601 }
2602
2603
2604
2605
2606 __PACKAGE__->register_method(
2607         method  => "user_retrieve_parts",
2608         api_name        => "open-ils.actor.user.retrieve.parts",);
2609
2610 sub user_retrieve_parts {
2611         my( $self, $client, $auth, $user_id, $fields ) = @_;
2612         my $e = new_editor(authtoken => $auth);
2613         return $e->event unless $e->checkauth;
2614         if( $e->requestor->id != $user_id ) {
2615                 return $e->event unless $e->allowed('VIEW_USER');
2616         }
2617         my @resp;
2618         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2619         push(@resp, $user->$_()) for(@$fields);
2620         return \@resp;
2621 }
2622
2623
2624
2625
2626
2627
2628 1;
2629