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