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