]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
user permission editor
[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 __PACKAGE__->register_method(
733         method  => "set_user_perms",
734         api_name        => "open-ils.actor.user.permissions.update",
735 );
736
737 sub set_user_perms {
738         my $self = shift;
739         my $client = shift;
740         my $ses = shift;
741         my $maps = shift;
742
743         my $session = $apputils->start_db_session();
744
745         my( $user_obj, $evt ) = $U->checkses($ses);
746         return $evt if $evt;
747
748         my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
749
750         my $all = undef;
751         $all = 1 if ($user_obj->super_user());
752     $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
753
754         for my $map (@$maps) {
755
756                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
757                 if ($map->isdeleted()) {
758                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
759                 } elsif ($map->isnew()) {
760                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
761                         $map->clear_id;
762                 }
763
764                 next unless ($all || (grep { $_->perm eq $map->perm and $_->grantable == 1 and $_->depth >= $map->depth } @$perms));
765
766                 #warn( "Updating permissions with method $method and session $ses and map $map" );
767                 $logger->info( "Updating permissions with method $method and map $map" );
768
769                 my $stat = $session->request($method, $map)->gather(1);
770                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
771
772         }
773
774         $apputils->commit_db_session($session);
775
776         return scalar(@$maps);
777 }
778
779
780 sub _create_standing_penalties {
781
782         my($session, $user_session, $patron, $new_patron) = @_;
783
784         my $maps = $patron->standing_penalties;
785         my $method;
786
787         for my $map (@$maps) {
788
789                 if ($map->isdeleted()) {
790                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
791                 } elsif ($map->isnew()) {
792                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
793                         $map->clear_id;
794                 } else {
795                         next;
796                 }
797
798                 $map->usr($new_patron->id);
799
800                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
801
802                 my $stat = $session->request($method, $map)->gather(1);
803                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
804         }
805
806         return ($new_patron, undef);
807 }
808
809
810
811 __PACKAGE__->register_method(
812         method  => "search_username",
813         api_name        => "open-ils.actor.user.search.username",
814 );
815
816 sub search_username {
817         my($self, $client, $username) = @_;
818         my $users = OpenILS::Application::AppUtils->simple_scalar_request(
819                         "open-ils.cstore", 
820                         "open-ils.cstore.direct.actor.user.search.atomic",
821                         { usrname => $username }
822         );
823         return $users;
824 }
825
826
827
828
829 __PACKAGE__->register_method(
830         method  => "user_retrieve_by_barcode",
831         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
832
833 sub user_retrieve_by_barcode {
834         my($self, $client, $user_session, $barcode) = @_;
835
836         $logger->debug("Searching for user with barcode $barcode");
837         my ($user_obj, $evt) = $apputils->checkses($user_session);
838         return $evt if $evt;
839
840         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
841                         "open-ils.cstore", 
842                         "open-ils.cstore.direct.actor.card.search.atomic",
843                         { barcode => $barcode }
844         );
845
846         if(!$card || !$card->[0]) {
847                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
848         }
849
850         $card = $card->[0];
851         my $user = flesh_user($card->usr());
852
853         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
854         return $evt if $evt;
855
856         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
857         return $user;
858
859 }
860
861
862
863 __PACKAGE__->register_method(
864         method  => "get_user_by_id",
865         api_name        => "open-ils.actor.user.retrieve",);
866
867 sub get_user_by_id {
868         my ($self, $client, $auth, $id) = @_;
869         my $e = new_editor(authtoken=>$auth);
870         return $e->event unless $e->checkauth;
871         my $user = $e->retrieve_actor_user($id)
872                 or return $e->event;
873         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
874         return $user;
875 }
876
877
878
879 __PACKAGE__->register_method(
880         method  => "get_org_types",
881         api_name        => "open-ils.actor.org_types.retrieve",);
882
883 my $org_types;
884 sub get_org_types {
885         my($self, $client) = @_;
886         return $org_types if $org_types;
887         return $org_types = new_editor()->retrieve_all_actor_org_unit_type();
888 }
889
890
891
892 __PACKAGE__->register_method(
893         method  => "get_user_ident_types",
894         api_name        => "open-ils.actor.user.ident_types.retrieve",
895 );
896 my $ident_types;
897 sub get_user_ident_types {
898         return $ident_types if $ident_types;
899         return $ident_types = 
900                 new_editor()->retrieve_all_config_identification_type();
901 }
902
903
904
905
906 __PACKAGE__->register_method(
907         method  => "get_org_unit",
908         api_name        => "open-ils.actor.org_unit.retrieve",
909 );
910
911 sub get_org_unit {
912         my( $self, $client, $user_session, $org_id ) = @_;
913         my $e = new_editor(authtoken => $user_session);
914         if(!$org_id) {
915                 return $e->event unless $e->checkauth;
916                 $org_id = $e->requestor->ws_ou;
917         }
918         my $o = $e->retrieve_actor_org_unit($org_id)
919                 or return $e->event;
920         return $o;
921 }
922
923 __PACKAGE__->register_method(
924         method  => "search_org_unit",
925         api_name        => "open-ils.actor.org_unit_list.search",
926 );
927
928 sub search_org_unit {
929
930         my( $self, $client, $field, $value ) = @_;
931
932         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
933                 "open-ils.cstore",
934                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
935                 { $field => $value } );
936
937         return $list;
938 }
939
940
941 # build the org tree
942
943 __PACKAGE__->register_method(
944         method  => "get_org_tree",
945         api_name        => "open-ils.actor.org_tree.retrieve",
946         argc            => 0, 
947         note            => "Returns the entire org tree structure",
948 );
949
950 sub get_org_tree {
951         my( $self, $client) = @_;
952
953         $cache  = OpenSRF::Utils::Cache->new("global", 0) unless $cache;
954         my $tree = $cache->get_cache('orgtree');
955         return $tree if $tree;
956
957         $tree = new_editor()->search_actor_org_unit( 
958                 [
959                         {"parent_ou" => undef },
960                         {
961                                 flesh                           => 2,
962                                 flesh_fields    => { aou =>  ['children'] },
963                                 order_by                        => { aou => 'name'}
964                         }
965                 ]
966         )->[0];
967
968         $cache->put_cache('orgtree', $tree);
969         return $tree;
970 }
971
972
973 # turns an org list into an org tree
974 sub build_org_tree {
975
976         my( $self, $orglist) = @_;
977
978         return $orglist unless ( 
979                         ref($orglist) and @$orglist > 1 );
980
981         my @list = sort { 
982                 $a->ou_type <=> $b->ou_type ||
983                 $a->name cmp $b->name } @$orglist;
984
985         for my $org (@list) {
986
987                 next unless ($org and defined($org->parent_ou));
988                 my ($parent) = grep { $_->id == $org->parent_ou } @list;
989                 next unless $parent;
990
991                 $parent->children([]) unless defined($parent->children); 
992                 push( @{$parent->children}, $org );
993         }
994
995         return $list[0];
996
997 }
998
999
1000 __PACKAGE__->register_method(
1001         method  => "get_org_descendants",
1002         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1003 );
1004
1005 # depth is optional.  org_unit is the id
1006 sub get_org_descendants {
1007         my( $self, $client, $org_unit, $depth ) = @_;
1008         my $orglist = $apputils->simple_scalar_request(
1009                         "open-ils.storage", 
1010                         "open-ils.storage.actor.org_unit.descendants.atomic",
1011                         $org_unit, $depth );
1012         return $self->build_org_tree($orglist);
1013 }
1014
1015
1016 __PACKAGE__->register_method(
1017         method  => "get_org_ancestors",
1018         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1019 );
1020
1021 # depth is optional.  org_unit is the id
1022 sub get_org_ancestors {
1023         my( $self, $client, $org_unit, $depth ) = @_;
1024         my $orglist = $apputils->simple_scalar_request(
1025                         "open-ils.storage", 
1026                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1027                         $org_unit, $depth );
1028         return $self->build_org_tree($orglist);
1029 }
1030
1031
1032 __PACKAGE__->register_method(
1033         method  => "get_standings",
1034         api_name        => "open-ils.actor.standings.retrieve"
1035 );
1036
1037 my $user_standings;
1038 sub get_standings {
1039         return $user_standings if $user_standings;
1040         return $user_standings = 
1041                 $apputils->simple_scalar_request(
1042                         "open-ils.cstore",
1043                         "open-ils.cstore.direct.config.standing.search.atomic",
1044                         { id => { "!=" => undef } }
1045                 );
1046 }
1047
1048
1049
1050 __PACKAGE__->register_method(
1051         method  => "get_my_org_path",
1052         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1053 );
1054
1055 sub get_my_org_path {
1056         my( $self, $client, $user_session, $org_id ) = @_;
1057         my $user_obj = $apputils->check_user_session($user_session); 
1058         if(!defined($org_id)) { $org_id = $user_obj->home_ou; }
1059
1060         return $apputils->simple_scalar_request(
1061                 "open-ils.storage",
1062                 "open-ils.storage.actor.org_unit.full_path.atomic",
1063                 $org_id );
1064 }
1065
1066
1067 __PACKAGE__->register_method(
1068         method  => "patron_adv_search",
1069         api_name        => "open-ils.actor.patron.search.advanced" );
1070 sub patron_adv_search {
1071         my( $self, $client, $auth, $search_hash, $search_limit, $search_sort, $include_inactive ) = @_;
1072         my $e = new_editor(authtoken=>$auth);
1073         return $e->event unless $e->checkauth;
1074         return $e->event unless $e->allowed('VIEW_USER');
1075         return $U->storagereq(
1076                 "open-ils.storage.actor.user.crazy_search", 
1077                 $search_hash, $search_limit, $search_sort, $include_inactive);
1078 }
1079
1080
1081
1082 sub _verify_password {
1083         my($user_session, $password) = @_;
1084         my $user_obj = $apputils->check_user_session($user_session); 
1085
1086         #grab the user with password
1087         $user_obj = $apputils->simple_scalar_request(
1088                 "open-ils.cstore", 
1089                 "open-ils.cstore.direct.actor.user.retrieve",
1090                 $user_obj->id );
1091
1092         if($user_obj->passwd eq $password) {
1093                 return 1;
1094         }
1095
1096         return 0;
1097 }
1098
1099
1100 __PACKAGE__->register_method(
1101         method  => "update_password",
1102         api_name        => "open-ils.actor.user.password.update");
1103
1104 __PACKAGE__->register_method(
1105         method  => "update_password",
1106         api_name        => "open-ils.actor.user.username.update");
1107
1108 __PACKAGE__->register_method(
1109         method  => "update_password",
1110         api_name        => "open-ils.actor.user.email.update");
1111
1112 sub update_password {
1113         my( $self, $client, $user_session, $new_value, $current_password ) = @_;
1114
1115         my $evt;
1116
1117         my $user_obj = $apputils->check_user_session($user_session); 
1118
1119         if($self->api_name =~ /password/o) {
1120
1121                 #make sure they know the current password
1122                 if(!_verify_password($user_session, md5_hex($current_password))) {
1123                         return OpenILS::Event->new('INCORRECT_PASSWORD');
1124                 }
1125
1126                 $logger->debug("update_password setting new password $new_value");
1127                 $user_obj->passwd($new_value);
1128
1129         } elsif($self->api_name =~ /username/o) {
1130                 my $users = search_username(undef, undef, $new_value); 
1131                 if( $users and $users->[0] ) {
1132                         return OpenILS::Event->new('USERNAME_EXISTS');
1133                 }
1134                 $user_obj->usrname($new_value);
1135
1136         } elsif($self->api_name =~ /email/o) {
1137                 #warn "Updating email to $new_value\n";
1138                 $user_obj->email($new_value);
1139         }
1140
1141         my $session = $apputils->start_db_session();
1142
1143         ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj, 1);
1144         return $evt if $evt;
1145
1146         $apputils->commit_db_session($session);
1147
1148         if($user_obj) { return 1; }
1149         return undef;
1150 }
1151
1152
1153 __PACKAGE__->register_method(
1154         method  => "check_user_perms",
1155         api_name        => "open-ils.actor.user.perm.check",
1156         notes           => <<"  NOTES");
1157         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1158         perm type, if the user does *not* have the given permission it is added
1159         to a list which is returned from the method.  If all permissions
1160         are allowed, an empty list is returned
1161         if the logged in user does not match 'user_id', then the logged in user must
1162         have VIEW_PERMISSION priveleges.
1163         NOTES
1164
1165 sub check_user_perms {
1166         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1167
1168         my( $staff, $evt ) = $apputils->checkses($login_session);
1169         return $evt if $evt;
1170
1171         if($staff->id ne $user_id) {
1172                 if( $evt = $apputils->check_perms(
1173                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1174                         return $evt;
1175                 }
1176         }
1177
1178         my @not_allowed;
1179         for my $perm (@$perm_types) {
1180                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1181                         push @not_allowed, $perm;
1182                 }
1183         }
1184
1185         return \@not_allowed
1186 }
1187
1188 __PACKAGE__->register_method(
1189         method  => "check_user_perms2",
1190         api_name        => "open-ils.actor.user.perm.check.multi_org",
1191         notes           => q/
1192                 Checks the permissions on a list of perms and orgs for a user
1193                 @param authtoken The login session key
1194                 @param user_id The id of the user to check
1195                 @param orgs The array of org ids
1196                 @param perms The array of permission names
1197                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1198                 if the logged in user does not match 'user_id', then the logged in user must
1199                 have VIEW_PERMISSION priveleges.
1200         /);
1201
1202 sub check_user_perms2 {
1203         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1204
1205         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1206                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1207         return $evt if $evt;
1208
1209         my @not_allowed;
1210         for my $org (@$orgs) {
1211                 for my $perm (@$perms) {
1212                         if($apputils->check_perms($user_id, $org, $perm)) {
1213                                 push @not_allowed, [ $org, $perm ];
1214                         }
1215                 }
1216         }
1217
1218         return \@not_allowed
1219 }
1220
1221
1222 __PACKAGE__->register_method(
1223         method => 'check_user_perms3',
1224         api_name        => 'open-ils.actor.user.perm.highest_org',
1225         notes           => q/
1226                 Returns the highest org unit id at which a user has a given permission
1227                 If the requestor does not match the target user, the requestor must have
1228                 'VIEW_PERMISSION' rights at the home org unit of the target user
1229                 @param authtoken The login session key
1230                 @param userid The id of the user in question
1231                 @param perm The permission to check
1232                 @return The org unit highest in the org tree within which the user has
1233                 the requested permission
1234         /);
1235
1236 sub check_user_perms3 {
1237         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1238
1239         my( $staff, $target, $org, $evt );
1240
1241         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1242                 $authtoken, $userid, 'VIEW_PERMISSION' );
1243         return $evt if $evt;
1244
1245         my $tree = $self->get_org_tree();
1246         return _find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1247 }
1248
1249
1250 sub _find_highest_perm_org {
1251         my ( $perm, $userid, $start_org, $org_tree ) = @_;
1252         my $org = $apputils->find_org($org_tree, $start_org );
1253
1254         my $lastid = undef;
1255         while( $org ) {
1256                 last if ($apputils->check_perms( $userid, $org->id, $perm )); # perm failed
1257                 $lastid = $org->id;
1258                 $org = $apputils->find_org( $org_tree, $org->parent_ou() );
1259         }
1260
1261         return $lastid;
1262 }
1263
1264 __PACKAGE__->register_method(
1265         method => 'check_user_perms4',
1266         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1267         notes           => q/
1268                 Returns the highest org unit id at which a user has a given permission
1269                 If the requestor does not match the target user, the requestor must have
1270                 'VIEW_PERMISSION' rights at the home org unit of the target user
1271                 @param authtoken The login session key
1272                 @param userid The id of the user in question
1273                 @param perms An array of perm names to check 
1274                 @return An array of orgId's  representing the org unit 
1275                 highest in the org tree within which the user has the requested permission
1276                 The arrah of orgId's has matches the order of the perms array
1277         /);
1278
1279 sub check_user_perms4 {
1280         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1281         
1282         my( $staff, $target, $org, $evt );
1283
1284         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1285                 $authtoken, $userid, 'VIEW_PERMISSION' );
1286         return $evt if $evt;
1287
1288         my @arr;
1289         return [] unless ref($perms);
1290         my $tree = $self->get_org_tree();
1291
1292         for my $p (@$perms) {
1293                 push( @arr, _find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1294         }
1295         return \@arr;
1296 }
1297
1298
1299
1300
1301 __PACKAGE__->register_method(
1302         method  => "user_fines_summary",
1303         api_name        => "open-ils.actor.user.fines.summary",
1304         notes           => <<"  NOTES");
1305         Returns a short summary of the users total open fines, excluding voided fines
1306         Params are login_session, user_id
1307         Returns a 'mous' object.
1308         NOTES
1309
1310 sub user_fines_summary {
1311         my( $self, $client, $login_session, $user_id ) = @_;
1312
1313         my $user_obj = $apputils->check_user_session($login_session); 
1314         if($user_obj->id ne $user_id) {
1315                 if($apputils->check_user_perms($user_obj->id, $user_obj->home_ou, "VIEW_USER_FINES_SUMMARY")) {
1316                         return OpenILS::Perm->new("VIEW_USER_FINES_SUMMARY"); 
1317                 }
1318         }
1319
1320         return $apputils->simple_scalar_request( 
1321                 "open-ils.cstore",
1322                 "open-ils.cstore.direct.money.open_user_summary.search",
1323                 { usr => $user_id } );
1324
1325 }
1326
1327
1328
1329
1330 __PACKAGE__->register_method(
1331         method  => "user_transactions",
1332         api_name        => "open-ils.actor.user.transactions",
1333         notes           => <<"  NOTES");
1334         Returns a list of open user transactions (mbts objects);
1335         Params are login_session, user_id
1336         Optional third parameter is the transactions type.  defaults to all
1337         NOTES
1338
1339 __PACKAGE__->register_method(
1340         method  => "user_transactions",
1341         api_name        => "open-ils.actor.user.transactions.have_charge",
1342         notes           => <<"  NOTES");
1343         Returns a list of all open user transactions (mbts objects) that have an initial charge
1344         Params are login_session, user_id
1345         Optional third parameter is the transactions type.  defaults to all
1346         NOTES
1347
1348 __PACKAGE__->register_method(
1349         method  => "user_transactions",
1350         api_name        => "open-ils.actor.user.transactions.have_balance",
1351         notes           => <<"  NOTES");
1352         Returns a list of all open user transactions (mbts objects) that have a balance
1353         Params are login_session, user_id
1354         Optional third parameter is the transactions type.  defaults to all
1355         NOTES
1356
1357 __PACKAGE__->register_method(
1358         method  => "user_transactions",
1359         api_name        => "open-ils.actor.user.transactions.fleshed",
1360         notes           => <<"  NOTES");
1361         Returns an object/hash of transaction, circ, title where transaction = an open 
1362         user transactions (mbts objects), circ is the attached circluation, and title
1363         is the title the circ points to
1364         Params are login_session, user_id
1365         Optional third parameter is the transactions type.  defaults to all
1366         NOTES
1367
1368 __PACKAGE__->register_method(
1369         method  => "user_transactions",
1370         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1371         notes           => <<"  NOTES");
1372         Returns an object/hash of transaction, circ, title where transaction = an open 
1373         user transactions that has an initial charge (mbts objects), circ is the 
1374         attached circluation, and title is the title the circ points to
1375         Params are login_session, user_id
1376         Optional third parameter is the transactions type.  defaults to all
1377         NOTES
1378
1379 __PACKAGE__->register_method(
1380         method  => "user_transactions",
1381         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1382         notes           => <<"  NOTES");
1383         Returns an object/hash of transaction, circ, title where transaction = an open 
1384         user transaction that has a balance (mbts objects), circ is the attached 
1385         circluation, and title is the title the circ points to
1386         Params are login_session, user_id
1387         Optional third parameter is the transaction type.  defaults to all
1388         NOTES
1389
1390 __PACKAGE__->register_method(
1391         method  => "user_transactions",
1392         api_name        => "open-ils.actor.user.transactions.count",
1393         notes           => <<"  NOTES");
1394         Returns an object/hash of transaction, circ, title where transaction = an open 
1395         user transactions (mbts objects), circ is the attached circluation, and title
1396         is the title the circ points to
1397         Params are login_session, user_id
1398         Optional third parameter is the transactions type.  defaults to all
1399         NOTES
1400
1401 __PACKAGE__->register_method(
1402         method  => "user_transactions",
1403         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1404         notes           => <<"  NOTES");
1405         Returns an object/hash of transaction, circ, title where transaction = an open 
1406         user transactions that has an initial charge (mbts objects), circ is the 
1407         attached circluation, and title is the title the circ points to
1408         Params are login_session, user_id
1409         Optional third parameter is the transactions type.  defaults to all
1410         NOTES
1411
1412 __PACKAGE__->register_method(
1413         method  => "user_transactions",
1414         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1415         notes           => <<"  NOTES");
1416         Returns an object/hash of transaction, circ, title where transaction = an open 
1417         user transaction that has a balance (mbts objects), circ is the attached 
1418         circluation, and title is the title the circ points to
1419         Params are login_session, user_id
1420         Optional third parameter is the transaction type.  defaults to all
1421         NOTES
1422
1423 __PACKAGE__->register_method(
1424         method  => "user_transactions",
1425         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1426         notes           => <<"  NOTES");
1427         Returns an object/hash of transaction, circ, title where transaction = an open 
1428         user transaction that has a balance (mbts objects), circ is the attached 
1429         circluation, and title is the title the circ points to
1430         Params are login_session, user_id
1431         Optional third parameter is the transaction type.  defaults to all
1432         NOTES
1433
1434
1435
1436 sub user_transactions {
1437         my( $self, $client, $login_session, $user_id, $type ) = @_;
1438
1439         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1440                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1441         return $evt if $evt;
1442
1443         my $api = $self->api_name();
1444         my $trans;
1445         my @xact;
1446
1447         if(defined($type)) { @xact = (xact_type =>  $type); 
1448
1449         } else { @xact = (); }
1450
1451         ($trans) = $self
1452                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1453                 ->run($login_session => $user_id => $type);
1454
1455         if($api =~ /have_charge/o) {
1456
1457                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1458
1459         } elsif($api =~ /have_balance/o) {
1460
1461                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1462         } else {
1463
1464                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1465
1466         }
1467
1468         if($api =~ /total/o) { 
1469                 my $total = 0.0;
1470                 for my $t (@$trans) {
1471                         $total += $t->balance_owed;
1472                 }
1473
1474                 $logger->debug("Total balance owed by user $user_id: $total");
1475                 return $total;
1476         }
1477
1478         if($api =~ /count/o) { return scalar @$trans; }
1479         if($api !~ /fleshed/o) { return $trans; }
1480
1481         my @resp;
1482         for my $t (@$trans) {
1483                         
1484                 if( $t->xact_type ne 'circulation' ) {
1485                         push @resp, {transaction => $t};
1486                         next;
1487                 }
1488
1489                 my $circ = $apputils->simple_scalar_request(
1490                                 "open-ils.cstore",
1491                                 "open-ils.cstore.direct.action.circulation.retrieve",
1492                                 $t->id );
1493
1494                 next unless $circ;
1495
1496                 my $title = $apputils->simple_scalar_request(
1497                         "open-ils.storage", 
1498                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1499                         $circ->target_copy );
1500
1501                 next unless $title;
1502
1503                 my $u = OpenILS::Utils::ModsParser->new();
1504                 $u->start_mods_batch($title->marc());
1505                 my $mods = $u->finish_mods_batch();
1506                 $mods->doc_id($title->id) if $mods;
1507
1508                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1509
1510         }
1511
1512         return \@resp; 
1513
1514
1515
1516 __PACKAGE__->register_method(
1517         method  => "user_transaction_retrieve",
1518         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1519         argc            => 1,
1520         notes           => <<"  NOTES");
1521         Returns a fleshedtransaction record
1522         NOTES
1523 __PACKAGE__->register_method(
1524         method  => "user_transaction_retrieve",
1525         api_name        => "open-ils.actor.user.transaction.retrieve",
1526         argc            => 1,
1527         notes           => <<"  NOTES");
1528         Returns a transaction record
1529         NOTES
1530 sub user_transaction_retrieve {
1531         my( $self, $client, $login_session, $bill_id ) = @_;
1532
1533         my $trans = $apputils->simple_scalar_request( 
1534                 "open-ils.cstore",
1535                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1536                 $bill_id
1537         );
1538
1539         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1540                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1541         return $evt if $evt;
1542         
1543         my $api = $self->api_name();
1544         if($api !~ /fleshed/o) { return $trans; }
1545
1546         if( $trans->xact_type ne 'circulation' ) {
1547                 $logger->debug("Returning non-circ transaction");
1548                 return {transaction => $trans};
1549         }
1550
1551         my $circ = $apputils->simple_scalar_request(
1552                         "open-ils.cstore",
1553                         "open-ils..direct.action.circulation.retrieve",
1554                         $trans->id );
1555
1556         return {transaction => $trans} unless $circ;
1557         $logger->debug("Found the circ transaction");
1558
1559         my $title = $apputils->simple_scalar_request(
1560                 "open-ils.storage", 
1561                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1562                 $circ->target_copy );
1563
1564         return {transaction => $trans, circ => $circ } unless $title;
1565         $logger->debug("Found the circ title");
1566
1567         my $mods;
1568         try {
1569                 my $u = OpenILS::Utils::ModsParser->new();
1570                 $u->start_mods_batch($title->marc());
1571                 $mods = $u->finish_mods_batch();
1572         } otherwise {
1573                 if ($title->id == OILS_PRECAT_RECORD) {
1574                         my $copy = $apputils->simple_scalar_request(
1575                                 "open-ils.cstore",
1576                                 "open-ils.cstore.direct.asset.copy.retrieve",
1577                                 $circ->target_copy );
1578
1579                         $mods = new Fieldmapper::metabib::virtual_record;
1580                         $mods->doc_id(OILS_PRECAT_RECORD);
1581                         $mods->title($copy->dummy_title);
1582                         $mods->author($copy->dummy_author);
1583                 }
1584         };
1585
1586         $logger->debug("MODSized the circ title");
1587
1588         return {transaction => $trans, circ => $circ, record => $mods };
1589 }
1590
1591
1592 __PACKAGE__->register_method(
1593         method  => "hold_request_count",
1594         api_name        => "open-ils.actor.user.hold_requests.count",
1595         argc            => 1,
1596         notes           => <<"  NOTES");
1597         Returns hold ready/total counts
1598         NOTES
1599 sub hold_request_count {
1600         my( $self, $client, $login_session, $userid ) = @_;
1601
1602         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1603                 $login_session, $userid, 'VIEW_HOLD' );
1604         return $evt if $evt;
1605         
1606
1607         my $holds = $apputils->simple_scalar_request(
1608                         "open-ils.cstore",
1609                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1610                         { 
1611                                 usr => $userid,
1612                                 fulfillment_time => {"=" => undef },
1613                                 cancel_time => undef,
1614                         }
1615         );
1616
1617         my @ready;
1618         for my $h (@$holds) {
1619                 next unless $h->capture_time and $h->current_copy;
1620
1621                 my $copy = $apputils->simple_scalar_request(
1622                         "open-ils.cstore",
1623                         "open-ils.cstore.direct.asset.copy.retrieve",
1624                         $h->current_copy
1625                 );
1626
1627                 if ($copy and $copy->status == 8) {
1628                         push @ready, $h;
1629                 }
1630         }
1631
1632         return { total => scalar(@$holds), ready => scalar(@ready) };
1633 }
1634
1635
1636 __PACKAGE__->register_method(
1637         method  => "checkedout_count",
1638         api_name        => "open-ils.actor.user.checked_out.count__",
1639         argc            => 1,
1640         notes           => <<"  NOTES");
1641         Returns a transaction record
1642         NOTES
1643
1644 # XXX Deprecate Me
1645 sub checkedout_count {
1646         my( $self, $client, $login_session, $userid ) = @_;
1647
1648         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1649                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1650         return $evt if $evt;
1651         
1652         my $circs = $apputils->simple_scalar_request(
1653                         "open-ils.cstore",
1654                         "open-ils.cstore.direct.action.circulation.search.atomic",
1655                         { usr => $userid, stop_fines => undef }
1656                         #{ usr => $userid, checkin_time => {"=" => undef } }
1657         );
1658
1659         my $parser = DateTime::Format::ISO8601->new;
1660
1661         my (@out,@overdue);
1662         for my $c (@$circs) {
1663                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1664                 my $due = $due_dt->epoch;
1665
1666                 if ($due < DateTime->today->epoch) {
1667                         push @overdue, $c;
1668                 }
1669         }
1670
1671         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1672 }
1673
1674
1675 __PACKAGE__->register_method(
1676         method          => "checked_out",
1677         api_name                => "open-ils.actor.user.checked_out",
1678         argc                    => 2,
1679         signature       => q/
1680                 Returns a structure of circulations objects sorted by
1681                 out, overdue, lost, claims_returned, long_overdue.
1682                 A list of IDs are returned of each type.
1683                 lost, long_overdue, and claims_returned circ will not
1684                 be "finished" (there is an outstanding balance or some 
1685                 other pending action on the circ). 
1686
1687                 The .count method also includes a 'total' field which 
1688                 sums all "open" circs
1689         /
1690 );
1691
1692 __PACKAGE__->register_method(
1693         method          => "checked_out",
1694         api_name                => "open-ils.actor.user.checked_out.count",
1695         argc                    => 2,
1696         signature       => q/@see open-ils.actor.user.checked_out/
1697 );
1698
1699 sub checked_out {
1700         my( $self, $conn, $auth, $userid ) = @_;
1701
1702         my $e = new_editor(authtoken=>$auth);
1703         return $e->event unless $e->checkauth;
1704
1705         if( $userid ne $e->requestor->id ) {
1706                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1707         }
1708
1709         my $count = $self->api_name =~ /count/;
1710         return _checked_out( $count, $e, $userid );
1711 }
1712
1713 sub _checked_out {
1714         my( $iscount, $e, $userid ) = @_;
1715
1716
1717         my $meth = 'open-ils.storage.actor.user.checked_out';
1718         $meth = "$meth.count" if $iscount;
1719         return $U->storagereq($meth, $userid);
1720
1721 # XXX Old code - moved to storage
1722 #------------------------------------------------------------------------------
1723 #------------------------------------------------------------------------------
1724         my $circs = $e->search_action_circulation( 
1725                 { usr => $userid, checkin_time => undef });
1726
1727         my $parser = DateTime::Format::ISO8601->new;
1728
1729         # split the circs up into overdue and not-overdue circs
1730         my (@out,@overdue);
1731         for my $c (@$circs) {
1732                 if( $c->due_date ) {
1733                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1734                         my $due = $due_dt->epoch;
1735                         if ($due < DateTime->today->epoch) {
1736                                 push @overdue, $c;
1737                         } else {
1738                                 push @out, $c;
1739                         }
1740                 } else {
1741                         push @out, $c;
1742                 }
1743         }
1744
1745         my( @open, @od, @lost, @cr, @lo );
1746
1747         while (my $c = shift(@out)) {
1748                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1749                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1750                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1751                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1752         }
1753
1754         while (my $c = shift(@overdue)) {
1755                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1756                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1757                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1758                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1759         }
1760
1761         if( $iscount ) {
1762                 return {
1763                         total           => @open + @od + @lost + @cr + @lo,
1764                         out             => scalar(@open),
1765                         overdue => scalar(@od),
1766                         lost            => scalar(@lost),
1767                         claims_returned => scalar(@cr),
1768                         long_overdue            => scalar(@lo)
1769                 };
1770         }
1771
1772         return {
1773                 out             => \@open,
1774                 overdue => \@od,
1775                 lost            => \@lost,
1776                 claims_returned => \@cr,
1777                 long_overdue            => \@lo
1778         };
1779 }
1780
1781
1782 sub _checked_out_WHAT {
1783         my( $iscount, $e, $userid ) = @_;
1784
1785         my $circs = $e->search_action_circulation( 
1786                 { usr => $userid, stop_fines => undef });
1787
1788         my $mcircs = $e->search_action_circulation( 
1789                 { 
1790                         usr => $userid, 
1791                         checkin_time => undef, 
1792                         xact_finish => undef, 
1793                 });
1794
1795         
1796         push( @$circs, @$mcircs );
1797
1798         my $parser = DateTime::Format::ISO8601->new;
1799
1800         # split the circs up into overdue and not-overdue circs
1801         my (@out,@overdue);
1802         for my $c (@$circs) {
1803                 if( $c->due_date ) {
1804                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1805                         my $due = $due_dt->epoch;
1806                         if ($due < DateTime->today->epoch) {
1807                                 push @overdue, $c->id;
1808                         } else {
1809                                 push @out, $c->id;
1810                         }
1811                 } else {
1812                         push @out, $c->id;
1813                 }
1814         }
1815
1816         # grab all of the lost, claims-returned, and longoverdue circs
1817         #my $open = $e->search_action_circulation(
1818         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1819
1820
1821         # these items have stop_fines, but no xact_finish, so money
1822         # is owed on them and they have not been checked in
1823         my $open = $e->search_action_circulation(
1824                 {
1825                         usr                             => $userid, 
1826                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1827                         xact_finish             => undef,
1828                         checkin_time    => undef,
1829                 }
1830         );
1831
1832
1833         my( @lost, @cr, @lo );
1834         for my $c (@$open) {
1835                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1836                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1837                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1838         }
1839
1840
1841         if( $iscount ) {
1842                 return {
1843                         total           => @$circs + @lost + @cr + @lo,
1844                         out             => scalar(@out),
1845                         overdue => scalar(@overdue),
1846                         lost            => scalar(@lost),
1847                         claims_returned => scalar(@cr),
1848                         long_overdue            => scalar(@lo)
1849                 };
1850         }
1851
1852         return {
1853                 out             => \@out,
1854                 overdue => \@overdue,
1855                 lost            => \@lost,
1856                 claims_returned => \@cr,
1857                 long_overdue            => \@lo
1858         };
1859 }
1860
1861
1862
1863 __PACKAGE__->register_method(
1864         method          => "checked_in_with_fines",
1865         api_name                => "open-ils.actor.user.checked_in_with_fines",
1866         argc                    => 2,
1867         signature       => q/@see open-ils.actor.user.checked_out/
1868 );
1869 sub checked_in_with_fines {
1870         my( $self, $conn, $auth, $userid ) = @_;
1871
1872         my $e = new_editor(authtoken=>$auth);
1873         return $e->event unless $e->checkauth;
1874
1875         if( $userid ne $e->requestor->id ) {
1876                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1877         }
1878
1879         # money is owed on these items and they are checked in
1880         my $open = $e->search_action_circulation(
1881                 {
1882                         usr                             => $userid, 
1883                         xact_finish             => undef,
1884                         checkin_time    => { "!=" => undef },
1885                 }
1886         );
1887
1888
1889         my( @lost, @cr, @lo );
1890         for my $c (@$open) {
1891                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1892                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1893                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1894         }
1895
1896         return {
1897                 lost            => \@lost,
1898                 claims_returned => \@cr,
1899                 long_overdue            => \@lo
1900         };
1901 }
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911 __PACKAGE__->register_method(
1912         method  => "user_transaction_history",
1913         api_name        => "open-ils.actor.user.transactions.history",
1914         argc            => 1,
1915         notes           => <<"  NOTES");
1916         Returns a list of billable transaction ids for a user, optionally by type
1917         NOTES
1918 __PACKAGE__->register_method(
1919         method  => "user_transaction_history",
1920         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1921         argc            => 1,
1922         notes           => <<"  NOTES");
1923         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1924         NOTES
1925 __PACKAGE__->register_method(
1926         method  => "user_transaction_history",
1927         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1928         argc            => 1,
1929         notes           => <<"  NOTES");
1930         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1931         NOTES
1932 __PACKAGE__->register_method(
1933         method  => "user_transaction_history",
1934         api_name        => "open-ils.actor.user.transactions.history.still_open",
1935         argc            => 1,
1936         notes           => <<"  NOTES");
1937         Returns a list of billable transaction ids for a user that are not finished
1938         NOTES
1939 __PACKAGE__->register_method(
1940         method  => "user_transaction_history",
1941         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1942         argc            => 1,
1943         notes           => <<"  NOTES");
1944         Returns a list of billable transaction ids for a user that has billings
1945         NOTES
1946
1947
1948
1949 =head old
1950 sub _user_transaction_history {
1951         my( $self, $client, $login_session, $user_id, $type ) = @_;
1952
1953         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1954                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1955         return $evt if $evt;
1956
1957         my $api = $self->api_name();
1958         my @xact;
1959         my @charge;
1960         my @balance;
1961
1962         @xact = (xact_type =>  $type) if(defined($type));
1963         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
1964         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
1965
1966         $logger->debug("searching for transaction history: @xact : @balance, @charge");
1967
1968         my $trans = $apputils->simple_scalar_request( 
1969                 "open-ils.cstore",
1970                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
1971                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
1972
1973         return [ map { $_->id } @$trans ];
1974 }
1975 =cut
1976
1977 sub _make_mbts {
1978         my @xacts = @_;
1979
1980         my @mbts;
1981         for my $x (@xacts) {
1982                 my $s = new Fieldmapper::money::billable_transaction_summary;
1983                 $s->id( $x->id );
1984                 $s->usr( $x->usr );
1985                 $s->xact_start( $x->xact_start );
1986                 $s->xact_finish( $x->xact_finish );
1987
1988                 my $to = 0;
1989                 my $lb = undef;
1990                 for my $b (@{ $x->billings }) {
1991                         next if ($U->is_true($b->voided));
1992                         $to += ($b->amount * 100);
1993                         $lb ||= $b->billing_ts;
1994                         if ($b->billing_ts ge $lb) {
1995                                 $lb = $b->billing_ts;
1996                                 $s->last_billing_note($b->note);
1997                                 $s->last_billing_ts($b->billing_ts);
1998                                 $s->last_billing_type($b->billing_type);
1999                         }
2000                 }
2001
2002                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2003
2004                 my $tp = 0;
2005                 my $lp = undef;
2006                 for my $p (@{ $x->payments }) {
2007                         next if ($U->is_true($p->voided));
2008                         $tp += ($p->amount * 100);
2009                         $lp ||= $p->payment_ts;
2010                         if ($p->payment_ts ge $lp) {
2011                                 $lp = $p->payment_ts;
2012                                 $s->last_payment_note($p->note);
2013                                 $s->last_payment_ts($p->payment_ts);
2014                                 $s->last_payment_type($p->payment_type);
2015                         }
2016                 }
2017                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2018
2019                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2020
2021                 $s->xact_type( 'grocery' ) if ($x->grocery);
2022                 $s->xact_type( 'circulation' ) if ($x->circulation);
2023
2024                 push @mbts, $s;
2025         }
2026
2027         return @mbts;
2028 }
2029
2030 sub user_transaction_history {
2031         my( $self, $conn, $auth, $userid, $type ) = @_;
2032         my $e = new_editor(authtoken=>$auth);
2033         return $e->event unless $e->checkauth;
2034         return $e->event unless $e->allowed('VIEW_USER_TRANSACTIONS');
2035
2036         my $api = $self->api_name;
2037         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2038
2039         my @xacts = @{ $e->search_money_billable_transaction(
2040                 [       { usr => $userid, @xact_finish },
2041                         { flesh => 1,
2042                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2043                           order_by => { mbt => 'xact_start DESC' },
2044                         }
2045                 ]
2046         ) };
2047
2048         my @mbts = _make_mbts( @xacts );
2049
2050         if(defined($type)) {
2051                 @mbts = grep { $_->xact_type eq $type } @mbts;
2052         }
2053
2054         if($api =~ /have_balance/o) {
2055                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2056         }
2057
2058         if($api =~ /have_charge/o) {
2059                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2060         }
2061
2062         if($api =~ /have_bill/o) {
2063                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2064         }
2065
2066         return [@mbts];
2067 }
2068
2069
2070
2071 __PACKAGE__->register_method(
2072         method  => "user_perms",
2073         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2074         argc            => 1,
2075         notes           => <<"  NOTES");
2076         Returns a list of permissions
2077         NOTES
2078 sub user_perms {
2079         my( $self, $client, $authtoken, $user ) = @_;
2080
2081         my( $staff, $evt ) = $apputils->checkses($authtoken);
2082         return $evt if $evt;
2083
2084         $user ||= $staff->id;
2085
2086         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2087                 return $evt;
2088         }
2089
2090         return $apputils->simple_scalar_request(
2091                 "open-ils.storage",
2092                 "open-ils.storage.permission.user_perms.atomic",
2093                 $user);
2094 }
2095
2096 __PACKAGE__->register_method(
2097         method  => "retrieve_perms",
2098         api_name        => "open-ils.actor.permissions.retrieve",
2099         notes           => <<"  NOTES");
2100         Returns a list of permissions
2101         NOTES
2102 sub retrieve_perms {
2103         my( $self, $client ) = @_;
2104         return $apputils->simple_scalar_request(
2105                 "open-ils.cstore",
2106                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2107                 { id => { '!=' => undef } }
2108         );
2109 }
2110
2111 __PACKAGE__->register_method(
2112         method  => "retrieve_groups",
2113         api_name        => "open-ils.actor.groups.retrieve",
2114         notes           => <<"  NOTES");
2115         Returns a list of user groupss
2116         NOTES
2117 sub retrieve_groups {
2118         my( $self, $client ) = @_;
2119         return new_editor()->retrieve_all_permission_grp_tree();
2120 }
2121
2122 __PACKAGE__->register_method(
2123         method  => "retrieve_org_address",
2124         api_name        => "open-ils.actor.org_unit.address.retrieve",
2125         notes           => <<'  NOTES');
2126         Returns an org_unit address by ID
2127         @param An org_address ID
2128         NOTES
2129 sub retrieve_org_address {
2130         my( $self, $client, $id ) = @_;
2131         return $apputils->simple_scalar_request(
2132                 "open-ils.cstore",
2133                 "open-ils.cstore.direct.actor.org_address.retrieve",
2134                 $id
2135         );
2136 }
2137
2138 __PACKAGE__->register_method(
2139         method  => "retrieve_groups_tree",
2140         api_name        => "open-ils.actor.groups.tree.retrieve",
2141         notes           => <<"  NOTES");
2142         Returns a list of user groups
2143         NOTES
2144 sub retrieve_groups_tree {
2145         my( $self, $client ) = @_;
2146         return new_editor()->search_permission_grp_tree(
2147                 [
2148                         { parent => undef},
2149                         {       
2150                                 flesh                           => 10, 
2151                                 flesh_fields    => { pgt => ["children"] }, 
2152                                 order_by                        => { pgt => 'name'}
2153                         }
2154                 ]
2155         )->[0];
2156 }
2157
2158
2159 # turns an org list into an org tree
2160 =head old code
2161 sub build_group_tree {
2162
2163         my( $self, $grplist) = @_;
2164
2165         return $grplist unless ( 
2166                         ref($grplist) and @$grplist > 1 );
2167
2168         my @list = sort { $a->name cmp $b->name } @$grplist;
2169
2170         my $root;
2171         for my $grp (@list) {
2172
2173                 if ($grp and !defined($grp->parent)) {
2174                         $root = $grp;
2175                         next;
2176                 }
2177                 my ($parent) = grep { $_->id == $grp->parent} @list;
2178
2179                 $parent->children([]) unless defined($parent->children); 
2180                 push( @{$parent->children}, $grp );
2181         }
2182
2183         return $root;
2184 }
2185 =cut
2186
2187
2188 __PACKAGE__->register_method(
2189         method  => "add_user_to_groups",
2190         api_name        => "open-ils.actor.user.set_groups",
2191         notes           => <<"  NOTES");
2192         Adds a user to one or more permission groups
2193         NOTES
2194
2195 sub add_user_to_groups {
2196         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2197
2198         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2199                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2200         return $evt if $evt;
2201
2202         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2203                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2204         return $evt if $evt;
2205
2206         $apputils->simplereq(
2207                 'open-ils.storage',
2208                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2209                 
2210         for my $group (@$groups) {
2211                 my $link = Fieldmapper::permission::usr_grp_map->new;
2212                 $link->grp($group);
2213                 $link->usr($userid);
2214
2215                 my $id = $apputils->simplereq(
2216                         'open-ils.storage',
2217                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2218         }
2219
2220         return 1;
2221 }
2222
2223 __PACKAGE__->register_method(
2224         method  => "get_user_perm_groups",
2225         api_name        => "open-ils.actor.user.get_groups",
2226         notes           => <<"  NOTES");
2227         Retrieve a user's permission groups.
2228         NOTES
2229
2230
2231 sub get_user_perm_groups {
2232         my( $self, $client, $authtoken, $userid ) = @_;
2233
2234         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2235                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2236         return $evt if $evt;
2237
2238         return $apputils->simplereq(
2239                 'open-ils.cstore',
2240                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2241 }       
2242
2243
2244
2245 __PACKAGE__->register_method (
2246         method          => 'register_workstation',
2247         api_name                => 'open-ils.actor.workstation.register.override',
2248         signature       => q/@see open-ils.actor.workstation.register/);
2249
2250 __PACKAGE__->register_method (
2251         method          => 'register_workstation',
2252         api_name                => 'open-ils.actor.workstation.register',
2253         signature       => q/
2254                 Registers a new workstion in the system
2255                 @param authtoken The login session key
2256                 @param name The name of the workstation id
2257                 @param owner The org unit that owns this workstation
2258                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2259                 if the name is already in use.
2260         /);
2261
2262 sub _register_workstation {
2263         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2264         my( $requestor, $evt ) = $U->checkses($authtoken);
2265         return $evt if $evt;
2266         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2267         return $evt if $evt;
2268
2269         my $ws = $U->cstorereq(
2270                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2271         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2272
2273         $ws = Fieldmapper::actor::workstation->new;
2274         $ws->owning_lib($owner);
2275         $ws->name($name);
2276
2277         my $id = $U->storagereq(
2278                 'open-ils.storage.direct.actor.workstation.create', $ws );
2279         return $U->DB_UPDATE_FAILED($ws) unless $id;
2280
2281         $ws->id($id);
2282         return $ws->id();
2283 }
2284
2285 sub register_workstation {
2286         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2287
2288         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2289         return $e->event unless $e->checkauth;
2290         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2291         my $existing = $e->search_actor_workstation({name => $name});
2292
2293         if( @$existing ) {
2294                 if( $self->api_name =~ /override/o ) {
2295                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2296                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2297                 } else {
2298                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2299                 }
2300         }
2301
2302         my $ws = Fieldmapper::actor::workstation->new;
2303         $ws->owning_lib($owner);
2304         $ws->name($name);
2305         $e->create_actor_workstation($ws) or return $e->event;
2306         $e->commit;
2307         return $ws->id; # note: editor sets the id on the new object for us
2308 }
2309
2310
2311 __PACKAGE__->register_method (
2312         method          => 'fetch_patron_note',
2313         api_name                => 'open-ils.actor.note.retrieve.all',
2314         signature       => q/
2315                 Returns a list of notes for a given user
2316                 Requestor must have VIEW_USER permission if pub==false and
2317                 @param authtoken The login session key
2318                 @param args Hash of params including
2319                         patronid : the patron's id
2320                         pub : true if retrieving only public notes
2321         /
2322 );
2323
2324 sub fetch_patron_note {
2325         my( $self, $conn, $authtoken, $args ) = @_;
2326         my $patronid = $$args{patronid};
2327
2328         my($reqr, $evt) = $U->checkses($authtoken);
2329         return $evt if $evt;
2330
2331         my $patron;
2332         ($patron, $evt) = $U->fetch_user($patronid);
2333         return $evt if $evt;
2334
2335         if($$args{pub}) {
2336                 if( $patronid ne $reqr->id ) {
2337                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2338                         return $evt if $evt;
2339                 }
2340                 return $U->cstorereq(
2341                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2342                         { usr => $patronid, pub => 't' } );
2343         }
2344
2345         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2346         return $evt if $evt;
2347
2348         return $U->cstorereq(
2349                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2350 }
2351
2352 __PACKAGE__->register_method (
2353         method          => 'create_user_note',
2354         api_name                => 'open-ils.actor.note.create',
2355         signature       => q/
2356                 Creates a new note for the given user
2357                 @param authtoken The login session key
2358                 @param note The note object
2359         /
2360 );
2361 sub create_user_note {
2362         my( $self, $conn, $authtoken, $note ) = @_;
2363         my( $reqr, $patron, $evt ) = 
2364                 $U->checkses_requestor($authtoken, $note->usr, 'UPDATE_USER');
2365         return $evt if $evt;
2366         $logger->activity("user ".$reqr->id." creating note for user ".$note->usr);
2367
2368         $note->creator($reqr->id);
2369         my $id = $U->storagereq(
2370                 'open-ils.storage.direct.actor.usr_note.create', $note );
2371         return $U->DB_UPDATE_FAILED($note) unless $id;
2372         return $id;
2373 }
2374
2375
2376 __PACKAGE__->register_method (
2377         method          => 'delete_user_note',
2378         api_name                => 'open-ils.actor.note.delete',
2379         signature       => q/
2380                 Deletes a note for the given user
2381                 @param authtoken The login session key
2382                 @param noteid The note id
2383         /
2384 );
2385 sub delete_user_note {
2386         my( $self, $conn, $authtoken, $noteid ) = @_;
2387
2388         my $note = $U->cstorereq(
2389                 'open-ils.cstore.direct.actor.usr_note.retrieve', $noteid);
2390         return OpenILS::Event->new('ACTOR_USER_NOTE_NOT_FOUND') unless $note;
2391
2392         my( $reqr, $patron, $evt ) = 
2393                 $U->checkses_requestor($authtoken, $note->usr, 'UPDATE_USER');
2394         return $evt if $evt;
2395         $logger->activity("user ".$reqr->id." deleting note [$noteid] for user ".$note->usr);
2396
2397         my $stat = $U->storagereq(
2398                 'open-ils.storage.direct.actor.usr_note.delete', $noteid );
2399         return $U->DB_UPDATE_FAILED($note) unless defined $stat;
2400         return $stat;
2401 }
2402
2403
2404 __PACKAGE__->register_method (
2405         method          => 'update_user_note',
2406         api_name                => 'open-ils.actor.note.update',
2407         signature       => q/
2408                 @param authtoken The login session key
2409                 @param note The note
2410         /
2411 );
2412
2413 sub update_user_note {
2414         my( $self, $conn, $auth, $note ) = @_;
2415         my $e = new_editor(authtoken=>$auth, xact=>1);
2416         return $e->event unless $e->checkauth;
2417         my $patron = $e->retrieve_actor_user($note->usr)
2418                 or return $e->event;
2419         return $e->event unless 
2420                 $e->allowed('UPDATE_USER', $patron->home_ou);
2421         $e->update_actor_user_note($note)
2422                 or return $e->event;
2423         $e->commit;
2424         return 1;
2425 }
2426
2427
2428
2429
2430 __PACKAGE__->register_method (
2431         method          => 'create_closed_date',
2432         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2433         signature       => q/
2434                 Creates a new closing entry for the given org_unit
2435                 @param authtoken The login session key
2436                 @param note The closed_date object
2437         /
2438 );
2439 sub create_closed_date {
2440         my( $self, $conn, $authtoken, $cd ) = @_;
2441
2442         my( $user, $evt ) = $U->checkses($authtoken);
2443         return $evt if $evt;
2444
2445         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2446         return $evt if $evt;
2447
2448         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2449
2450         my $id = $U->storagereq(
2451                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2452         return $U->DB_UPDATE_FAILED($cd) unless $id;
2453         return $id;
2454 }
2455
2456
2457 __PACKAGE__->register_method (
2458         method          => 'delete_closed_date',
2459         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2460         signature       => q/
2461                 Deletes a closing entry for the given org_unit
2462                 @param authtoken The login session key
2463                 @param noteid The close_date id
2464         /
2465 );
2466 sub delete_closed_date {
2467         my( $self, $conn, $authtoken, $cd ) = @_;
2468
2469         my( $user, $evt ) = $U->checkses($authtoken);
2470         return $evt if $evt;
2471
2472         my $cd_obj;
2473         ($cd_obj, $evt) = fetch_closed_date($cd);
2474         return $evt if $evt;
2475
2476         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2477         return $evt if $evt;
2478
2479         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2480
2481         my $stat = $U->storagereq(
2482                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2483         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2484         return $stat;
2485 }
2486
2487
2488 __PACKAGE__->register_method(
2489         method => 'usrname_exists',
2490         api_name        => 'open-ils.actor.username.exists',
2491         signature => q/
2492                 Returns 1 if the requested username exists, returns 0 otherwise
2493         /
2494 );
2495
2496 sub usrname_exists {
2497         my( $self, $conn, $auth, $usrname ) = @_;
2498         my $e = new_editor(authtoken=>$auth);
2499         return $e->event unless $e->checkauth;
2500         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2501         return $$a[0] if $a and @$a;
2502         return 0;
2503 }
2504
2505 __PACKAGE__->register_method(
2506         method => 'barcode_exists',
2507         api_name        => 'open-ils.actor.barcode.exists',
2508         signature => q/
2509                 Returns 1 if the requested barcode exists, returns 0 otherwise
2510         /
2511 );
2512
2513 sub barcode_exists {
2514         my( $self, $conn, $auth, $barcode ) = @_;
2515         my $e = new_editor(authtoken=>$auth);
2516         return $e->event unless $e->checkauth;
2517         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2518         return $$a[0] if $a and @$a;
2519         return 0;
2520 }
2521
2522
2523 __PACKAGE__->register_method(
2524         method => 'retrieve_net_levels',
2525         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2526 );
2527
2528 sub retrieve_net_levels {
2529         my( $self, $conn, $auth ) = @_;
2530         my $e = new_editor(authtoken=>$auth);
2531         return $e->event unless $e->checkauth;
2532         return $e->retrieve_all_config_net_access_level();
2533 }
2534
2535
2536 __PACKAGE__->register_method(
2537         method => 'fetch_org_by_shortname',
2538         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2539 );
2540 sub fetch_org_by_shortname {
2541         my( $self, $conn, $sname ) = @_;
2542         my $e = new_editor();
2543         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2544         return $e->event unless $org;
2545         return $org;
2546 }
2547
2548
2549 __PACKAGE__->register_method(
2550         method => 'session_home_lib',
2551         api_name => 'open-ils.actor.session.home_lib',
2552 );
2553
2554 sub session_home_lib {
2555         my( $self, $conn, $auth ) = @_;
2556         my $e = new_editor(authtoken=>$auth);
2557         return undef unless $e->checkauth;
2558         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2559         return $org->shortname;
2560 }
2561
2562
2563
2564 __PACKAGE__->register_method(
2565         method => 'slim_tree',
2566         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2567 );
2568 sub slim_tree {
2569         my $tree = new_editor()->search_actor_org_unit( 
2570                 [
2571                         {"parent_ou" => undef },
2572                         {
2573                                 flesh                           => 2,
2574                                 flesh_fields    => { aou =>  ['children'] },
2575                                 order_by                        => { aou => 'name'},
2576                                 select                  => { aou => ["id","shortname", "name"]},
2577                         }
2578                 ]
2579         )->[0];
2580
2581         return trim_tree($tree);
2582 }
2583
2584
2585 sub trim_tree {
2586         my $tree = shift;
2587         return undef unless $tree;
2588         my $htree = {
2589                 code => $tree->shortname,
2590                 name => $tree->name,
2591         };
2592         if( $tree->children and @{$tree->children} ) {
2593                 $htree->{children} = [];
2594                 for my $c (@{$tree->children}) {
2595                         push( @{$htree->{children}}, trim_tree($c) );
2596                 }
2597         }
2598
2599         return $htree;
2600 }
2601
2602
2603
2604 __PACKAGE__->register_method(
2605         method  => "user_retrieve_fleshed_by_id",
2606         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2607
2608 sub user_retrieve_fleshed_by_id {
2609         my( $self, $client, $auth, $user_id, $fields ) = @_;
2610         my $e = new_editor(authtoken => $auth);
2611         return $e->event unless $e->checkauth;
2612         if( $e->requestor->id != $user_id ) {
2613                 return $e->event unless $e->allowed('VIEW_USER');
2614         }
2615         $fields ||= [
2616                 "cards",
2617                 "card",
2618                 "standing_penalties",
2619                 "addresses",
2620                 "billing_address",
2621                 "mailing_address",
2622                 "stat_cat_entries" ];
2623         return new_flesh_user($user_id, $fields, $e);
2624 }
2625
2626
2627 sub new_flesh_user {
2628
2629         my $id = shift;
2630         my $fields = shift || [];
2631         my $e   = shift || new_editor(xact=>1);
2632
2633         my $user = $e->retrieve_actor_user(
2634         [
2635         $id,
2636         {
2637                 "flesh"                         => 1,
2638                 "flesh_fields" =>  { "au" => $fields }
2639         }
2640         ]
2641         ) or return $e->event;
2642
2643
2644         if( grep { $_ eq 'addresses' } @$fields ) {
2645
2646                 $user->addresses([]) unless @{$user->addresses};
2647         
2648                 if( ref $user->billing_address ) {
2649                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2650                                 push( @{$user->addresses}, $user->billing_address );
2651                         }
2652                 }
2653         
2654                 if( ref $user->mailing_address ) {
2655                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2656                                 push( @{$user->addresses}, $user->mailing_address );
2657                         }
2658                 }
2659         }
2660
2661         $e->disconnect;
2662         $user->clear_passwd();
2663         return $user;
2664 }
2665
2666
2667
2668
2669 __PACKAGE__->register_method(
2670         method  => "user_retrieve_parts",
2671         api_name        => "open-ils.actor.user.retrieve.parts",);
2672
2673 sub user_retrieve_parts {
2674         my( $self, $client, $auth, $user_id, $fields ) = @_;
2675         my $e = new_editor(authtoken => $auth);
2676         return $e->event unless $e->checkauth;
2677         if( $e->requestor->id != $user_id ) {
2678                 return $e->event unless $e->allowed('VIEW_USER');
2679         }
2680         my @resp;
2681         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2682         push(@resp, $user->$_()) for(@$fields);
2683         return \@resp;
2684 }
2685
2686
2687
2688
2689
2690
2691 1;
2692