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