]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
not requiring perm for retrieving your own transaction data
[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 if (!$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, $auth, $user_id ) = @_;
1312         my $e = new_editor(authtoken=>$auth);
1313         return $e->event unless $e->checkauth;
1314         my $user = $e->retrieve_actor_user($user_id)
1315                 or return $e->event;
1316
1317         if( $user_id ne $e->requestor->id ) {
1318                 return $e->event unless 
1319                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1320         }
1321         
1322         # run this inside a transaction to prevent replication delay errors
1323         my $ses = $U->start_db_session();
1324         my $s = $ses->request(
1325                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1326         $U->rollback_db_session($ses);
1327         return $s;
1328 }
1329
1330
1331
1332
1333 __PACKAGE__->register_method(
1334         method  => "user_transactions",
1335         api_name        => "open-ils.actor.user.transactions",
1336         notes           => <<"  NOTES");
1337         Returns a list of open user transactions (mbts objects);
1338         Params are login_session, user_id
1339         Optional third parameter is the transactions type.  defaults to all
1340         NOTES
1341
1342 __PACKAGE__->register_method(
1343         method  => "user_transactions",
1344         api_name        => "open-ils.actor.user.transactions.have_charge",
1345         notes           => <<"  NOTES");
1346         Returns a list of all open user transactions (mbts objects) that have an initial charge
1347         Params are login_session, user_id
1348         Optional third parameter is the transactions type.  defaults to all
1349         NOTES
1350
1351 __PACKAGE__->register_method(
1352         method  => "user_transactions",
1353         api_name        => "open-ils.actor.user.transactions.have_balance",
1354         notes           => <<"  NOTES");
1355         Returns a list of all open user transactions (mbts objects) that have a balance
1356         Params are login_session, user_id
1357         Optional third parameter is the transactions type.  defaults to all
1358         NOTES
1359
1360 __PACKAGE__->register_method(
1361         method  => "user_transactions",
1362         api_name        => "open-ils.actor.user.transactions.fleshed",
1363         notes           => <<"  NOTES");
1364         Returns an object/hash of transaction, circ, title where transaction = an open 
1365         user transactions (mbts objects), circ is the attached circluation, and title
1366         is the title the circ points to
1367         Params are login_session, user_id
1368         Optional third parameter is the transactions type.  defaults to all
1369         NOTES
1370
1371 __PACKAGE__->register_method(
1372         method  => "user_transactions",
1373         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1374         notes           => <<"  NOTES");
1375         Returns an object/hash of transaction, circ, title where transaction = an open 
1376         user transactions that has an initial charge (mbts objects), circ is the 
1377         attached circluation, and title is the title the circ points to
1378         Params are login_session, user_id
1379         Optional third parameter is the transactions type.  defaults to all
1380         NOTES
1381
1382 __PACKAGE__->register_method(
1383         method  => "user_transactions",
1384         api_name        => "open-ils.actor.user.transactions.have_balance.fleshed",
1385         notes           => <<"  NOTES");
1386         Returns an object/hash of transaction, circ, title where transaction = an open 
1387         user transaction that has a balance (mbts objects), circ is the attached 
1388         circluation, and title is the title the circ points to
1389         Params are login_session, user_id
1390         Optional third parameter is the transaction type.  defaults to all
1391         NOTES
1392
1393 __PACKAGE__->register_method(
1394         method  => "user_transactions",
1395         api_name        => "open-ils.actor.user.transactions.count",
1396         notes           => <<"  NOTES");
1397         Returns an object/hash of transaction, circ, title where transaction = an open 
1398         user transactions (mbts objects), circ is the attached circluation, and title
1399         is the title the circ points to
1400         Params are login_session, user_id
1401         Optional third parameter is the transactions type.  defaults to all
1402         NOTES
1403
1404 __PACKAGE__->register_method(
1405         method  => "user_transactions",
1406         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1407         notes           => <<"  NOTES");
1408         Returns an object/hash of transaction, circ, title where transaction = an open 
1409         user transactions that has an initial charge (mbts objects), circ is the 
1410         attached circluation, and title is the title the circ points to
1411         Params are login_session, user_id
1412         Optional third parameter is the transactions type.  defaults to all
1413         NOTES
1414
1415 __PACKAGE__->register_method(
1416         method  => "user_transactions",
1417         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1418         notes           => <<"  NOTES");
1419         Returns an object/hash of transaction, circ, title where transaction = an open 
1420         user transaction that has a balance (mbts objects), circ is the attached 
1421         circluation, and title is the title the circ points to
1422         Params are login_session, user_id
1423         Optional third parameter is the transaction type.  defaults to all
1424         NOTES
1425
1426 __PACKAGE__->register_method(
1427         method  => "user_transactions",
1428         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1429         notes           => <<"  NOTES");
1430         Returns an object/hash of transaction, circ, title where transaction = an open 
1431         user transaction that has a balance (mbts objects), circ is the attached 
1432         circluation, and title is the title the circ points to
1433         Params are login_session, user_id
1434         Optional third parameter is the transaction type.  defaults to all
1435         NOTES
1436
1437
1438
1439 sub user_transactions {
1440         my( $self, $client, $login_session, $user_id, $type ) = @_;
1441
1442         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1443                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1444         return $evt if $evt;
1445
1446         my $api = $self->api_name();
1447         my $trans;
1448         my @xact;
1449
1450         if(defined($type)) { @xact = (xact_type =>  $type); 
1451
1452         } else { @xact = (); }
1453
1454         ($trans) = $self
1455                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1456                 ->run($login_session => $user_id => $type);
1457
1458         if($api =~ /have_charge/o) {
1459
1460                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1461
1462         } elsif($api =~ /have_balance/o) {
1463
1464                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1465         } else {
1466
1467                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1468
1469         }
1470
1471         if($api =~ /total/o) { 
1472                 my $total = 0.0;
1473                 for my $t (@$trans) {
1474                         $total += $t->balance_owed;
1475                 }
1476
1477                 $logger->debug("Total balance owed by user $user_id: $total");
1478                 return $total;
1479         }
1480
1481         if($api =~ /count/o) { return scalar @$trans; }
1482         if($api !~ /fleshed/o) { return $trans; }
1483
1484         my @resp;
1485         for my $t (@$trans) {
1486                         
1487                 if( $t->xact_type ne 'circulation' ) {
1488                         push @resp, {transaction => $t};
1489                         next;
1490                 }
1491
1492                 my $circ = $apputils->simple_scalar_request(
1493                                 "open-ils.cstore",
1494                                 "open-ils.cstore.direct.action.circulation.retrieve",
1495                                 $t->id );
1496
1497                 next unless $circ;
1498
1499                 my $title = $apputils->simple_scalar_request(
1500                         "open-ils.storage", 
1501                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1502                         $circ->target_copy );
1503
1504                 next unless $title;
1505
1506                 my $u = OpenILS::Utils::ModsParser->new();
1507                 $u->start_mods_batch($title->marc());
1508                 my $mods = $u->finish_mods_batch();
1509                 $mods->doc_id($title->id) if $mods;
1510
1511                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1512
1513         }
1514
1515         return \@resp; 
1516
1517
1518
1519 __PACKAGE__->register_method(
1520         method  => "user_transaction_retrieve",
1521         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1522         argc            => 1,
1523         notes           => <<"  NOTES");
1524         Returns a fleshedtransaction record
1525         NOTES
1526 __PACKAGE__->register_method(
1527         method  => "user_transaction_retrieve",
1528         api_name        => "open-ils.actor.user.transaction.retrieve",
1529         argc            => 1,
1530         notes           => <<"  NOTES");
1531         Returns a transaction record
1532         NOTES
1533 sub user_transaction_retrieve {
1534         my( $self, $client, $login_session, $bill_id ) = @_;
1535
1536         my $trans = $apputils->simple_scalar_request( 
1537                 "open-ils.cstore",
1538                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1539                 $bill_id
1540         );
1541
1542         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1543                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1544         return $evt if $evt;
1545         
1546         my $api = $self->api_name();
1547         if($api !~ /fleshed/o) { return $trans; }
1548
1549         if( $trans->xact_type ne 'circulation' ) {
1550                 $logger->debug("Returning non-circ transaction");
1551                 return {transaction => $trans};
1552         }
1553
1554         my $circ = $apputils->simple_scalar_request(
1555                         "open-ils.cstore",
1556                         "open-ils..direct.action.circulation.retrieve",
1557                         $trans->id );
1558
1559         return {transaction => $trans} unless $circ;
1560         $logger->debug("Found the circ transaction");
1561
1562         my $title = $apputils->simple_scalar_request(
1563                 "open-ils.storage", 
1564                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1565                 $circ->target_copy );
1566
1567         return {transaction => $trans, circ => $circ } unless $title;
1568         $logger->debug("Found the circ title");
1569
1570         my $mods;
1571         try {
1572                 my $u = OpenILS::Utils::ModsParser->new();
1573                 $u->start_mods_batch($title->marc());
1574                 $mods = $u->finish_mods_batch();
1575         } otherwise {
1576                 if ($title->id == OILS_PRECAT_RECORD) {
1577                         my $copy = $apputils->simple_scalar_request(
1578                                 "open-ils.cstore",
1579                                 "open-ils.cstore.direct.asset.copy.retrieve",
1580                                 $circ->target_copy );
1581
1582                         $mods = new Fieldmapper::metabib::virtual_record;
1583                         $mods->doc_id(OILS_PRECAT_RECORD);
1584                         $mods->title($copy->dummy_title);
1585                         $mods->author($copy->dummy_author);
1586                 }
1587         };
1588
1589         $logger->debug("MODSized the circ title");
1590
1591         return {transaction => $trans, circ => $circ, record => $mods };
1592 }
1593
1594
1595 __PACKAGE__->register_method(
1596         method  => "hold_request_count",
1597         api_name        => "open-ils.actor.user.hold_requests.count",
1598         argc            => 1,
1599         notes           => <<"  NOTES");
1600         Returns hold ready/total counts
1601         NOTES
1602 sub hold_request_count {
1603         my( $self, $client, $login_session, $userid ) = @_;
1604
1605         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1606                 $login_session, $userid, 'VIEW_HOLD' );
1607         return $evt if $evt;
1608         
1609
1610         my $holds = $apputils->simple_scalar_request(
1611                         "open-ils.cstore",
1612                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1613                         { 
1614                                 usr => $userid,
1615                                 fulfillment_time => {"=" => undef },
1616                                 cancel_time => undef,
1617                         }
1618         );
1619
1620         my @ready;
1621         for my $h (@$holds) {
1622                 next unless $h->capture_time and $h->current_copy;
1623
1624                 my $copy = $apputils->simple_scalar_request(
1625                         "open-ils.cstore",
1626                         "open-ils.cstore.direct.asset.copy.retrieve",
1627                         $h->current_copy
1628                 );
1629
1630                 if ($copy and $copy->status == 8) {
1631                         push @ready, $h;
1632                 }
1633         }
1634
1635         return { total => scalar(@$holds), ready => scalar(@ready) };
1636 }
1637
1638
1639 __PACKAGE__->register_method(
1640         method  => "checkedout_count",
1641         api_name        => "open-ils.actor.user.checked_out.count__",
1642         argc            => 1,
1643         notes           => <<"  NOTES");
1644         Returns a transaction record
1645         NOTES
1646
1647 # XXX Deprecate Me
1648 sub checkedout_count {
1649         my( $self, $client, $login_session, $userid ) = @_;
1650
1651         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1652                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1653         return $evt if $evt;
1654         
1655         my $circs = $apputils->simple_scalar_request(
1656                         "open-ils.cstore",
1657                         "open-ils.cstore.direct.action.circulation.search.atomic",
1658                         { usr => $userid, stop_fines => undef }
1659                         #{ usr => $userid, checkin_time => {"=" => undef } }
1660         );
1661
1662         my $parser = DateTime::Format::ISO8601->new;
1663
1664         my (@out,@overdue);
1665         for my $c (@$circs) {
1666                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1667                 my $due = $due_dt->epoch;
1668
1669                 if ($due < DateTime->today->epoch) {
1670                         push @overdue, $c;
1671                 }
1672         }
1673
1674         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1675 }
1676
1677
1678 __PACKAGE__->register_method(
1679         method          => "checked_out",
1680         api_name                => "open-ils.actor.user.checked_out",
1681         argc                    => 2,
1682         signature       => q/
1683                 Returns a structure of circulations objects sorted by
1684                 out, overdue, lost, claims_returned, long_overdue.
1685                 A list of IDs are returned of each type.
1686                 lost, long_overdue, and claims_returned circ will not
1687                 be "finished" (there is an outstanding balance or some 
1688                 other pending action on the circ). 
1689
1690                 The .count method also includes a 'total' field which 
1691                 sums all "open" circs
1692         /
1693 );
1694
1695 __PACKAGE__->register_method(
1696         method          => "checked_out",
1697         api_name                => "open-ils.actor.user.checked_out.count",
1698         argc                    => 2,
1699         signature       => q/@see open-ils.actor.user.checked_out/
1700 );
1701
1702 sub checked_out {
1703         my( $self, $conn, $auth, $userid ) = @_;
1704
1705         my $e = new_editor(authtoken=>$auth);
1706         return $e->event unless $e->checkauth;
1707
1708         if( $userid ne $e->requestor->id ) {
1709                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1710         }
1711
1712         my $count = $self->api_name =~ /count/;
1713         return _checked_out( $count, $e, $userid );
1714 }
1715
1716 sub _checked_out {
1717         my( $iscount, $e, $userid ) = @_;
1718
1719
1720         my $meth = 'open-ils.storage.actor.user.checked_out';
1721         $meth = "$meth.count" if $iscount;
1722         return $U->storagereq($meth, $userid);
1723
1724 # XXX Old code - moved to storage
1725 #------------------------------------------------------------------------------
1726 #------------------------------------------------------------------------------
1727         my $circs = $e->search_action_circulation( 
1728                 { usr => $userid, checkin_time => undef });
1729
1730         my $parser = DateTime::Format::ISO8601->new;
1731
1732         # split the circs up into overdue and not-overdue circs
1733         my (@out,@overdue);
1734         for my $c (@$circs) {
1735                 if( $c->due_date ) {
1736                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1737                         my $due = $due_dt->epoch;
1738                         if ($due < DateTime->today->epoch) {
1739                                 push @overdue, $c;
1740                         } else {
1741                                 push @out, $c;
1742                         }
1743                 } else {
1744                         push @out, $c;
1745                 }
1746         }
1747
1748         my( @open, @od, @lost, @cr, @lo );
1749
1750         while (my $c = shift(@out)) {
1751                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1752                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1753                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1754                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1755         }
1756
1757         while (my $c = shift(@overdue)) {
1758                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1759                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1760                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1761                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1762         }
1763
1764         if( $iscount ) {
1765                 return {
1766                         total           => @open + @od + @lost + @cr + @lo,
1767                         out             => scalar(@open),
1768                         overdue => scalar(@od),
1769                         lost            => scalar(@lost),
1770                         claims_returned => scalar(@cr),
1771                         long_overdue            => scalar(@lo)
1772                 };
1773         }
1774
1775         return {
1776                 out             => \@open,
1777                 overdue => \@od,
1778                 lost            => \@lost,
1779                 claims_returned => \@cr,
1780                 long_overdue            => \@lo
1781         };
1782 }
1783
1784
1785 sub _checked_out_WHAT {
1786         my( $iscount, $e, $userid ) = @_;
1787
1788         my $circs = $e->search_action_circulation( 
1789                 { usr => $userid, stop_fines => undef });
1790
1791         my $mcircs = $e->search_action_circulation( 
1792                 { 
1793                         usr => $userid, 
1794                         checkin_time => undef, 
1795                         xact_finish => undef, 
1796                 });
1797
1798         
1799         push( @$circs, @$mcircs );
1800
1801         my $parser = DateTime::Format::ISO8601->new;
1802
1803         # split the circs up into overdue and not-overdue circs
1804         my (@out,@overdue);
1805         for my $c (@$circs) {
1806                 if( $c->due_date ) {
1807                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1808                         my $due = $due_dt->epoch;
1809                         if ($due < DateTime->today->epoch) {
1810                                 push @overdue, $c->id;
1811                         } else {
1812                                 push @out, $c->id;
1813                         }
1814                 } else {
1815                         push @out, $c->id;
1816                 }
1817         }
1818
1819         # grab all of the lost, claims-returned, and longoverdue circs
1820         #my $open = $e->search_action_circulation(
1821         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1822
1823
1824         # these items have stop_fines, but no xact_finish, so money
1825         # is owed on them and they have not been checked in
1826         my $open = $e->search_action_circulation(
1827                 {
1828                         usr                             => $userid, 
1829                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1830                         xact_finish             => undef,
1831                         checkin_time    => undef,
1832                 }
1833         );
1834
1835
1836         my( @lost, @cr, @lo );
1837         for my $c (@$open) {
1838                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1839                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1840                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1841         }
1842
1843
1844         if( $iscount ) {
1845                 return {
1846                         total           => @$circs + @lost + @cr + @lo,
1847                         out             => scalar(@out),
1848                         overdue => scalar(@overdue),
1849                         lost            => scalar(@lost),
1850                         claims_returned => scalar(@cr),
1851                         long_overdue            => scalar(@lo)
1852                 };
1853         }
1854
1855         return {
1856                 out             => \@out,
1857                 overdue => \@overdue,
1858                 lost            => \@lost,
1859                 claims_returned => \@cr,
1860                 long_overdue            => \@lo
1861         };
1862 }
1863
1864
1865
1866 __PACKAGE__->register_method(
1867         method          => "checked_in_with_fines",
1868         api_name                => "open-ils.actor.user.checked_in_with_fines",
1869         argc                    => 2,
1870         signature       => q/@see open-ils.actor.user.checked_out/
1871 );
1872 sub checked_in_with_fines {
1873         my( $self, $conn, $auth, $userid ) = @_;
1874
1875         my $e = new_editor(authtoken=>$auth);
1876         return $e->event unless $e->checkauth;
1877
1878         if( $userid ne $e->requestor->id ) {
1879                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1880         }
1881
1882         # money is owed on these items and they are checked in
1883         my $open = $e->search_action_circulation(
1884                 {
1885                         usr                             => $userid, 
1886                         xact_finish             => undef,
1887                         checkin_time    => { "!=" => undef },
1888                 }
1889         );
1890
1891
1892         my( @lost, @cr, @lo );
1893         for my $c (@$open) {
1894                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1895                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1896                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1897         }
1898
1899         return {
1900                 lost            => \@lost,
1901                 claims_returned => \@cr,
1902                 long_overdue            => \@lo
1903         };
1904 }
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914 __PACKAGE__->register_method(
1915         method  => "user_transaction_history",
1916         api_name        => "open-ils.actor.user.transactions.history",
1917         argc            => 1,
1918         notes           => <<"  NOTES");
1919         Returns a list of billable transaction ids for a user, optionally by type
1920         NOTES
1921 __PACKAGE__->register_method(
1922         method  => "user_transaction_history",
1923         api_name        => "open-ils.actor.user.transactions.history.have_charge",
1924         argc            => 1,
1925         notes           => <<"  NOTES");
1926         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
1927         NOTES
1928 __PACKAGE__->register_method(
1929         method  => "user_transaction_history",
1930         api_name        => "open-ils.actor.user.transactions.history.have_balance",
1931         argc            => 1,
1932         notes           => <<"  NOTES");
1933         Returns a list of billable transaction ids for a user that have a balance, optionally by type
1934         NOTES
1935 __PACKAGE__->register_method(
1936         method  => "user_transaction_history",
1937         api_name        => "open-ils.actor.user.transactions.history.still_open",
1938         argc            => 1,
1939         notes           => <<"  NOTES");
1940         Returns a list of billable transaction ids for a user that are not finished
1941         NOTES
1942 __PACKAGE__->register_method(
1943         method  => "user_transaction_history",
1944         api_name        => "open-ils.actor.user.transactions.history.have_bill",
1945         argc            => 1,
1946         notes           => <<"  NOTES");
1947         Returns a list of billable transaction ids for a user that has billings
1948         NOTES
1949
1950
1951
1952 =head old
1953 sub _user_transaction_history {
1954         my( $self, $client, $login_session, $user_id, $type ) = @_;
1955
1956         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1957                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1958         return $evt if $evt;
1959
1960         my $api = $self->api_name();
1961         my @xact;
1962         my @charge;
1963         my @balance;
1964
1965         @xact = (xact_type =>  $type) if(defined($type));
1966         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
1967         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
1968
1969         $logger->debug("searching for transaction history: @xact : @balance, @charge");
1970
1971         my $trans = $apputils->simple_scalar_request( 
1972                 "open-ils.cstore",
1973                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
1974                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
1975
1976         return [ map { $_->id } @$trans ];
1977 }
1978 =cut
1979
1980 =head SEE APPUTILS.PM
1981 sub _make_mbts {
1982         my @xacts = @_;
1983
1984         my @mbts;
1985         for my $x (@xacts) {
1986                 my $s = new Fieldmapper::money::billable_transaction_summary;
1987                 $s->id( $x->id );
1988                 $s->usr( $x->usr );
1989                 $s->xact_start( $x->xact_start );
1990                 $s->xact_finish( $x->xact_finish );
1991
1992                 my $to = 0;
1993                 my $lb = undef;
1994                 for my $b (@{ $x->billings }) {
1995                         next if ($U->is_true($b->voided));
1996                         $to += ($b->amount * 100);
1997                         $lb ||= $b->billing_ts;
1998                         if ($b->billing_ts ge $lb) {
1999                                 $lb = $b->billing_ts;
2000                                 $s->last_billing_note($b->note);
2001                                 $s->last_billing_ts($b->billing_ts);
2002                                 $s->last_billing_type($b->billing_type);
2003                         }
2004                 }
2005
2006                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2007
2008                 my $tp = 0;
2009                 my $lp = undef;
2010                 for my $p (@{ $x->payments }) {
2011                         next if ($U->is_true($p->voided));
2012                         $tp += ($p->amount * 100);
2013                         $lp ||= $p->payment_ts;
2014                         if ($p->payment_ts ge $lp) {
2015                                 $lp = $p->payment_ts;
2016                                 $s->last_payment_note($p->note);
2017                                 $s->last_payment_ts($p->payment_ts);
2018                                 $s->last_payment_type($p->payment_type);
2019                         }
2020                 }
2021                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2022
2023                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2024
2025                 $s->xact_type( 'grocery' ) if ($x->grocery);
2026                 $s->xact_type( 'circulation' ) if ($x->circulation);
2027
2028                 push @mbts, $s;
2029         }
2030
2031         return @mbts;
2032 }
2033 =cut
2034
2035 sub user_transaction_history {
2036         my( $self, $conn, $auth, $userid, $type ) = @_;
2037
2038         # run inside of a transaction to prevent replication delays
2039         my $e = new_editor(xact=>1, authtoken=>$auth);
2040         return $e->die_event unless $e->checkauth;
2041
2042         if( $e->requestor->id ne $userid ) {
2043                 return $e->die_event 
2044                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2045         }
2046
2047         my $api = $self->api_name;
2048         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2049
2050         my @xacts = @{ $e->search_money_billable_transaction(
2051                 [       { usr => $userid, @xact_finish },
2052                         { flesh => 1,
2053                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2054                           order_by => { mbt => 'xact_start DESC' },
2055                         }
2056                 ]
2057         ) };
2058
2059         $e->rollback;
2060
2061         #my @mbts = _make_mbts( @xacts );
2062         my @mbts = $U->make_mbts( @xacts );
2063
2064         if(defined($type)) {
2065                 @mbts = grep { $_->xact_type eq $type } @mbts;
2066         }
2067
2068         if($api =~ /have_balance/o) {
2069                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2070         }
2071
2072         if($api =~ /have_charge/o) {
2073                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2074         }
2075
2076         if($api =~ /have_bill/o) {
2077                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2078         }
2079
2080         return [@mbts];
2081 }
2082
2083
2084
2085 __PACKAGE__->register_method(
2086         method  => "user_perms",
2087         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2088         argc            => 1,
2089         notes           => <<"  NOTES");
2090         Returns a list of permissions
2091         NOTES
2092 sub user_perms {
2093         my( $self, $client, $authtoken, $user ) = @_;
2094
2095         my( $staff, $evt ) = $apputils->checkses($authtoken);
2096         return $evt if $evt;
2097
2098         $user ||= $staff->id;
2099
2100         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2101                 return $evt;
2102         }
2103
2104         return $apputils->simple_scalar_request(
2105                 "open-ils.storage",
2106                 "open-ils.storage.permission.user_perms.atomic",
2107                 $user);
2108 }
2109
2110 __PACKAGE__->register_method(
2111         method  => "retrieve_perms",
2112         api_name        => "open-ils.actor.permissions.retrieve",
2113         notes           => <<"  NOTES");
2114         Returns a list of permissions
2115         NOTES
2116 sub retrieve_perms {
2117         my( $self, $client ) = @_;
2118         return $apputils->simple_scalar_request(
2119                 "open-ils.cstore",
2120                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2121                 { id => { '!=' => undef } }
2122         );
2123 }
2124
2125 __PACKAGE__->register_method(
2126         method  => "retrieve_groups",
2127         api_name        => "open-ils.actor.groups.retrieve",
2128         notes           => <<"  NOTES");
2129         Returns a list of user groupss
2130         NOTES
2131 sub retrieve_groups {
2132         my( $self, $client ) = @_;
2133         return new_editor()->retrieve_all_permission_grp_tree();
2134 }
2135
2136 __PACKAGE__->register_method(
2137         method  => "retrieve_org_address",
2138         api_name        => "open-ils.actor.org_unit.address.retrieve",
2139         notes           => <<'  NOTES');
2140         Returns an org_unit address by ID
2141         @param An org_address ID
2142         NOTES
2143 sub retrieve_org_address {
2144         my( $self, $client, $id ) = @_;
2145         return $apputils->simple_scalar_request(
2146                 "open-ils.cstore",
2147                 "open-ils.cstore.direct.actor.org_address.retrieve",
2148                 $id
2149         );
2150 }
2151
2152 __PACKAGE__->register_method(
2153         method  => "retrieve_groups_tree",
2154         api_name        => "open-ils.actor.groups.tree.retrieve",
2155         notes           => <<"  NOTES");
2156         Returns a list of user groups
2157         NOTES
2158 sub retrieve_groups_tree {
2159         my( $self, $client ) = @_;
2160         return new_editor()->search_permission_grp_tree(
2161                 [
2162                         { parent => undef},
2163                         {       
2164                                 flesh                           => 10, 
2165                                 flesh_fields    => { pgt => ["children"] }, 
2166                                 order_by                        => { pgt => 'name'}
2167                         }
2168                 ]
2169         )->[0];
2170 }
2171
2172
2173 # turns an org list into an org tree
2174 =head old code
2175 sub build_group_tree {
2176
2177         my( $self, $grplist) = @_;
2178
2179         return $grplist unless ( 
2180                         ref($grplist) and @$grplist > 1 );
2181
2182         my @list = sort { $a->name cmp $b->name } @$grplist;
2183
2184         my $root;
2185         for my $grp (@list) {
2186
2187                 if ($grp and !defined($grp->parent)) {
2188                         $root = $grp;
2189                         next;
2190                 }
2191                 my ($parent) = grep { $_->id == $grp->parent} @list;
2192
2193                 $parent->children([]) unless defined($parent->children); 
2194                 push( @{$parent->children}, $grp );
2195         }
2196
2197         return $root;
2198 }
2199 =cut
2200
2201
2202 __PACKAGE__->register_method(
2203         method  => "add_user_to_groups",
2204         api_name        => "open-ils.actor.user.set_groups",
2205         notes           => <<"  NOTES");
2206         Adds a user to one or more permission groups
2207         NOTES
2208
2209 sub add_user_to_groups {
2210         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2211
2212         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2213                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2214         return $evt if $evt;
2215
2216         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2217                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2218         return $evt if $evt;
2219
2220         $apputils->simplereq(
2221                 'open-ils.storage',
2222                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2223                 
2224         for my $group (@$groups) {
2225                 my $link = Fieldmapper::permission::usr_grp_map->new;
2226                 $link->grp($group);
2227                 $link->usr($userid);
2228
2229                 my $id = $apputils->simplereq(
2230                         'open-ils.storage',
2231                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2232         }
2233
2234         return 1;
2235 }
2236
2237 __PACKAGE__->register_method(
2238         method  => "get_user_perm_groups",
2239         api_name        => "open-ils.actor.user.get_groups",
2240         notes           => <<"  NOTES");
2241         Retrieve a user's permission groups.
2242         NOTES
2243
2244
2245 sub get_user_perm_groups {
2246         my( $self, $client, $authtoken, $userid ) = @_;
2247
2248         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2249                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2250         return $evt if $evt;
2251
2252         return $apputils->simplereq(
2253                 'open-ils.cstore',
2254                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2255 }       
2256
2257
2258
2259 __PACKAGE__->register_method (
2260         method          => 'register_workstation',
2261         api_name                => 'open-ils.actor.workstation.register.override',
2262         signature       => q/@see open-ils.actor.workstation.register/);
2263
2264 __PACKAGE__->register_method (
2265         method          => 'register_workstation',
2266         api_name                => 'open-ils.actor.workstation.register',
2267         signature       => q/
2268                 Registers a new workstion in the system
2269                 @param authtoken The login session key
2270                 @param name The name of the workstation id
2271                 @param owner The org unit that owns this workstation
2272                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2273                 if the name is already in use.
2274         /);
2275
2276 sub _register_workstation {
2277         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2278         my( $requestor, $evt ) = $U->checkses($authtoken);
2279         return $evt if $evt;
2280         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2281         return $evt if $evt;
2282
2283         my $ws = $U->cstorereq(
2284                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2285         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2286
2287         $ws = Fieldmapper::actor::workstation->new;
2288         $ws->owning_lib($owner);
2289         $ws->name($name);
2290
2291         my $id = $U->storagereq(
2292                 'open-ils.storage.direct.actor.workstation.create', $ws );
2293         return $U->DB_UPDATE_FAILED($ws) unless $id;
2294
2295         $ws->id($id);
2296         return $ws->id();
2297 }
2298
2299 sub register_workstation {
2300         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2301
2302         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2303         return $e->event unless $e->checkauth;
2304         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2305         my $existing = $e->search_actor_workstation({name => $name});
2306
2307         if( @$existing ) {
2308                 if( $self->api_name =~ /override/o ) {
2309                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2310                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2311                 } else {
2312                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2313                 }
2314         }
2315
2316         my $ws = Fieldmapper::actor::workstation->new;
2317         $ws->owning_lib($owner);
2318         $ws->name($name);
2319         $e->create_actor_workstation($ws) or return $e->event;
2320         $e->commit;
2321         return $ws->id; # note: editor sets the id on the new object for us
2322 }
2323
2324
2325 __PACKAGE__->register_method (
2326         method          => 'fetch_patron_note',
2327         api_name                => 'open-ils.actor.note.retrieve.all',
2328         signature       => q/
2329                 Returns a list of notes for a given user
2330                 Requestor must have VIEW_USER permission if pub==false and
2331                 @param authtoken The login session key
2332                 @param args Hash of params including
2333                         patronid : the patron's id
2334                         pub : true if retrieving only public notes
2335         /
2336 );
2337
2338 sub fetch_patron_note {
2339         my( $self, $conn, $authtoken, $args ) = @_;
2340         my $patronid = $$args{patronid};
2341
2342         my($reqr, $evt) = $U->checkses($authtoken);
2343         return $evt if $evt;
2344
2345         my $patron;
2346         ($patron, $evt) = $U->fetch_user($patronid);
2347         return $evt if $evt;
2348
2349         if($$args{pub}) {
2350                 if( $patronid ne $reqr->id ) {
2351                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2352                         return $evt if $evt;
2353                 }
2354                 return $U->cstorereq(
2355                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2356                         { usr => $patronid, pub => 't' } );
2357         }
2358
2359         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2360         return $evt if $evt;
2361
2362         return $U->cstorereq(
2363                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2364 }
2365
2366 __PACKAGE__->register_method (
2367         method          => 'create_user_note',
2368         api_name                => 'open-ils.actor.note.create',
2369         signature       => q/
2370                 Creates a new note for the given user
2371                 @param authtoken The login session key
2372                 @param note The note object
2373         /
2374 );
2375 sub create_user_note {
2376         my( $self, $conn, $authtoken, $note ) = @_;
2377         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2378         return $e->die_event unless $e->checkauth;
2379
2380         my $user = $e->retrieve_actor_user($note->usr)
2381                 or return $e->die_event;
2382
2383         return $e->die_event unless 
2384                 $e->allowed('UPDATE_USER',$user->home_ou);
2385
2386         $note->creator($e->requestor->id);
2387         $e->create_actor_usr_note($note) or return $e->die_event;
2388         $e->commit;
2389         return $note->id;
2390 }
2391
2392
2393 __PACKAGE__->register_method (
2394         method          => 'delete_user_note',
2395         api_name                => 'open-ils.actor.note.delete',
2396         signature       => q/
2397                 Deletes a note for the given user
2398                 @param authtoken The login session key
2399                 @param noteid The note id
2400         /
2401 );
2402 sub delete_user_note {
2403         my( $self, $conn, $authtoken, $noteid ) = @_;
2404
2405         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2406         return $e->die_event unless $e->checkauth;
2407         my $note = $e->retrieve_actor_usr_note($noteid)
2408                 or return $e->die_event;
2409         my $user = $e->retrieve_actor_user($note->usr)
2410                 or return $e->die_event;
2411         return $e->die_event unless 
2412                 $e->allowed('UPDATE_USER', $user->home_ou);
2413         
2414         $e->delete_actor_usr_note($note) or return $e->die_event;
2415         $e->commit;
2416         return 1;
2417 }
2418
2419
2420 __PACKAGE__->register_method (
2421         method          => 'update_user_note',
2422         api_name                => 'open-ils.actor.note.update',
2423         signature       => q/
2424                 @param authtoken The login session key
2425                 @param note The note
2426         /
2427 );
2428
2429 sub update_user_note {
2430         my( $self, $conn, $auth, $note ) = @_;
2431         my $e = new_editor(authtoken=>$auth, xact=>1);
2432         return $e->event unless $e->checkauth;
2433         my $patron = $e->retrieve_actor_user($note->usr)
2434                 or return $e->event;
2435         return $e->event unless 
2436                 $e->allowed('UPDATE_USER', $patron->home_ou);
2437         $e->update_actor_user_note($note)
2438                 or return $e->event;
2439         $e->commit;
2440         return 1;
2441 }
2442
2443
2444
2445
2446 __PACKAGE__->register_method (
2447         method          => 'create_closed_date',
2448         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2449         signature       => q/
2450                 Creates a new closing entry for the given org_unit
2451                 @param authtoken The login session key
2452                 @param note The closed_date object
2453         /
2454 );
2455 sub create_closed_date {
2456         my( $self, $conn, $authtoken, $cd ) = @_;
2457
2458         my( $user, $evt ) = $U->checkses($authtoken);
2459         return $evt if $evt;
2460
2461         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2462         return $evt if $evt;
2463
2464         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2465
2466         my $id = $U->storagereq(
2467                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2468         return $U->DB_UPDATE_FAILED($cd) unless $id;
2469         return $id;
2470 }
2471
2472
2473 __PACKAGE__->register_method (
2474         method          => 'delete_closed_date',
2475         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2476         signature       => q/
2477                 Deletes a closing entry for the given org_unit
2478                 @param authtoken The login session key
2479                 @param noteid The close_date id
2480         /
2481 );
2482 sub delete_closed_date {
2483         my( $self, $conn, $authtoken, $cd ) = @_;
2484
2485         my( $user, $evt ) = $U->checkses($authtoken);
2486         return $evt if $evt;
2487
2488         my $cd_obj;
2489         ($cd_obj, $evt) = fetch_closed_date($cd);
2490         return $evt if $evt;
2491
2492         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2493         return $evt if $evt;
2494
2495         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2496
2497         my $stat = $U->storagereq(
2498                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2499         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2500         return $stat;
2501 }
2502
2503
2504 __PACKAGE__->register_method(
2505         method => 'usrname_exists',
2506         api_name        => 'open-ils.actor.username.exists',
2507         signature => q/
2508                 Returns 1 if the requested username exists, returns 0 otherwise
2509         /
2510 );
2511
2512 sub usrname_exists {
2513         my( $self, $conn, $auth, $usrname ) = @_;
2514         my $e = new_editor(authtoken=>$auth);
2515         return $e->event unless $e->checkauth;
2516         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2517         return $$a[0] if $a and @$a;
2518         return 0;
2519 }
2520
2521 __PACKAGE__->register_method(
2522         method => 'barcode_exists',
2523         api_name        => 'open-ils.actor.barcode.exists',
2524         signature => q/
2525                 Returns 1 if the requested barcode exists, returns 0 otherwise
2526         /
2527 );
2528
2529 sub barcode_exists {
2530         my( $self, $conn, $auth, $barcode ) = @_;
2531         my $e = new_editor(authtoken=>$auth);
2532         return $e->event unless $e->checkauth;
2533         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2534         return $$a[0] if $a and @$a;
2535         return 0;
2536 }
2537
2538
2539 __PACKAGE__->register_method(
2540         method => 'retrieve_net_levels',
2541         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2542 );
2543
2544 sub retrieve_net_levels {
2545         my( $self, $conn, $auth ) = @_;
2546         my $e = new_editor(authtoken=>$auth);
2547         return $e->event unless $e->checkauth;
2548         return $e->retrieve_all_config_net_access_level();
2549 }
2550
2551
2552 __PACKAGE__->register_method(
2553         method => 'fetch_org_by_shortname',
2554         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2555 );
2556 sub fetch_org_by_shortname {
2557         my( $self, $conn, $sname ) = @_;
2558         my $e = new_editor();
2559         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2560         return $e->event unless $org;
2561         return $org;
2562 }
2563
2564
2565 __PACKAGE__->register_method(
2566         method => 'session_home_lib',
2567         api_name => 'open-ils.actor.session.home_lib',
2568 );
2569
2570 sub session_home_lib {
2571         my( $self, $conn, $auth ) = @_;
2572         my $e = new_editor(authtoken=>$auth);
2573         return undef unless $e->checkauth;
2574         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2575         return $org->shortname;
2576 }
2577
2578
2579
2580 __PACKAGE__->register_method(
2581         method => 'slim_tree',
2582         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2583 );
2584 sub slim_tree {
2585         my $tree = new_editor()->search_actor_org_unit( 
2586                 [
2587                         {"parent_ou" => undef },
2588                         {
2589                                 flesh                           => 2,
2590                                 flesh_fields    => { aou =>  ['children'] },
2591                                 order_by                        => { aou => 'name'},
2592                                 select                  => { aou => ["id","shortname", "name"]},
2593                         }
2594                 ]
2595         )->[0];
2596
2597         return trim_tree($tree);
2598 }
2599
2600
2601 sub trim_tree {
2602         my $tree = shift;
2603         return undef unless $tree;
2604         my $htree = {
2605                 code => $tree->shortname,
2606                 name => $tree->name,
2607         };
2608         if( $tree->children and @{$tree->children} ) {
2609                 $htree->{children} = [];
2610                 for my $c (@{$tree->children}) {
2611                         push( @{$htree->{children}}, trim_tree($c) );
2612                 }
2613         }
2614
2615         return $htree;
2616 }
2617
2618
2619 __PACKAGE__->register_method(
2620         method  => "update_penalties",
2621         api_name        => "open-ils.actor.user.penalties.update");
2622 sub update_penalties {
2623         my( $self, $conn, $auth, $userid ) = @_;
2624         my $e = new_editor(authtoken=>$auth);
2625         return $e->event unless $e->checkauth;
2626         $U->update_patron_penalties( 
2627                 authtoken => $auth,
2628                 patronid  => $userid,
2629         );
2630         return 1;
2631 }
2632
2633
2634
2635 __PACKAGE__->register_method(
2636         method  => "user_retrieve_fleshed_by_id",
2637         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2638
2639 sub user_retrieve_fleshed_by_id {
2640         my( $self, $client, $auth, $user_id, $fields ) = @_;
2641         my $e = new_editor(authtoken => $auth);
2642         return $e->event unless $e->checkauth;
2643
2644         if( $e->requestor->id != $user_id ) {
2645                 return $e->event unless $e->allowed('VIEW_USER');
2646         }
2647
2648         $fields ||= [
2649                 "cards",
2650                 "card",
2651                 "standing_penalties",
2652                 "addresses",
2653                 "billing_address",
2654                 "mailing_address",
2655                 "stat_cat_entries" ];
2656         return new_flesh_user($user_id, $fields, $e);
2657 }
2658
2659
2660 sub new_flesh_user {
2661
2662         my $id = shift;
2663         my $fields = shift || [];
2664         my $e   = shift || new_editor(xact=>1);
2665
2666         my $user = $e->retrieve_actor_user(
2667         [
2668         $id,
2669         {
2670                 "flesh"                         => 1,
2671                 "flesh_fields" =>  { "au" => $fields }
2672         }
2673         ]
2674         ) or return $e->event;
2675
2676
2677         if( grep { $_ eq 'addresses' } @$fields ) {
2678
2679                 $user->addresses([]) unless @{$user->addresses};
2680         
2681                 if( ref $user->billing_address ) {
2682                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2683                                 push( @{$user->addresses}, $user->billing_address );
2684                         }
2685                 }
2686         
2687                 if( ref $user->mailing_address ) {
2688                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2689                                 push( @{$user->addresses}, $user->mailing_address );
2690                         }
2691                 }
2692         }
2693
2694         $e->rollback;
2695         $user->clear_passwd();
2696         return $user;
2697 }
2698
2699
2700
2701
2702 __PACKAGE__->register_method(
2703         method  => "user_retrieve_parts",
2704         api_name        => "open-ils.actor.user.retrieve.parts",);
2705
2706 sub user_retrieve_parts {
2707         my( $self, $client, $auth, $user_id, $fields ) = @_;
2708         my $e = new_editor(authtoken => $auth);
2709         return $e->event unless $e->checkauth;
2710         if( $e->requestor->id != $user_id ) {
2711                 return $e->event unless $e->allowed('VIEW_USER');
2712         }
2713         my @resp;
2714         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2715         push(@resp, $user->$_()) for(@$fields);
2716         return \@resp;
2717 }
2718
2719
2720
2721
2722
2723
2724 1;
2725