]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
9330f96af7d3e9d84991de6bfb5738dd6411e7a4
[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                             return $e->die_event unless $e->allowed('UPDATE_WORKSTATION', $owner); 
2468
2469                 $existing->owning_lib($owner);
2470                             return $e->die_event unless $e->update_actor_workstation($existing);
2471
2472                 $e->commit;
2473
2474             } else {
2475                 $logger->info(  
2476                     "attempt to register an existing workstation.  returning existing ID");
2477             }
2478
2479             return $existing->id;
2480
2481                 } else {
2482                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2483                 }
2484         }
2485
2486         my $ws = Fieldmapper::actor::workstation->new;
2487         $ws->owning_lib($owner);
2488         $ws->name($name);
2489         $e->create_actor_workstation($ws) or return $e->die_event;
2490         $e->commit;
2491         return $ws->id; # note: editor sets the id on the new object for us
2492 }
2493
2494 __PACKAGE__->register_method (
2495         method          => 'workstation_list',
2496         api_name                => 'open-ils.actor.workstation.list',
2497         signature       => q/
2498                 Returns a list of workstations registered at the given location
2499                 @param authtoken The login session key
2500                 @param ids A list of org_unit.id's for the workstation owners
2501         /);
2502
2503 sub workstation_list {
2504         my( $self, $conn, $authtoken, @orgs ) = @_;
2505
2506         my $e = new_editor(authtoken=>$authtoken);
2507         return $e->event unless $e->checkauth;
2508     my %results;
2509
2510     for my $o (@orgs) {
2511             return $e->event 
2512             unless $e->allowed('REGISTER_WORKSTATION', $o);
2513         $results{$o} = $e->search_actor_workstation({owning_lib=>$o});
2514     }
2515     return \%results;
2516 }
2517
2518
2519
2520
2521
2522
2523
2524 __PACKAGE__->register_method (
2525         method          => 'fetch_patron_note',
2526         api_name                => 'open-ils.actor.note.retrieve.all',
2527         signature       => q/
2528                 Returns a list of notes for a given user
2529                 Requestor must have VIEW_USER permission if pub==false and
2530                 @param authtoken The login session key
2531                 @param args Hash of params including
2532                         patronid : the patron's id
2533                         pub : true if retrieving only public notes
2534         /
2535 );
2536
2537 sub fetch_patron_note {
2538         my( $self, $conn, $authtoken, $args ) = @_;
2539         my $patronid = $$args{patronid};
2540
2541         my($reqr, $evt) = $U->checkses($authtoken);
2542         return $evt if $evt;
2543
2544         my $patron;
2545         ($patron, $evt) = $U->fetch_user($patronid);
2546         return $evt if $evt;
2547
2548         if($$args{pub}) {
2549                 if( $patronid ne $reqr->id ) {
2550                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2551                         return $evt if $evt;
2552                 }
2553                 return $U->cstorereq(
2554                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2555                         { usr => $patronid, pub => 't' } );
2556         }
2557
2558         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2559         return $evt if $evt;
2560
2561         return $U->cstorereq(
2562                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2563 }
2564
2565 __PACKAGE__->register_method (
2566         method          => 'create_user_note',
2567         api_name                => 'open-ils.actor.note.create',
2568         signature       => q/
2569                 Creates a new note for the given user
2570                 @param authtoken The login session key
2571                 @param note The note object
2572         /
2573 );
2574 sub create_user_note {
2575         my( $self, $conn, $authtoken, $note ) = @_;
2576         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2577         return $e->die_event unless $e->checkauth;
2578
2579         my $user = $e->retrieve_actor_user($note->usr)
2580                 or return $e->die_event;
2581
2582         return $e->die_event unless 
2583                 $e->allowed('UPDATE_USER',$user->home_ou);
2584
2585         $note->creator($e->requestor->id);
2586         $e->create_actor_usr_note($note) or return $e->die_event;
2587         $e->commit;
2588         return $note->id;
2589 }
2590
2591
2592 __PACKAGE__->register_method (
2593         method          => 'delete_user_note',
2594         api_name                => 'open-ils.actor.note.delete',
2595         signature       => q/
2596                 Deletes a note for the given user
2597                 @param authtoken The login session key
2598                 @param noteid The note id
2599         /
2600 );
2601 sub delete_user_note {
2602         my( $self, $conn, $authtoken, $noteid ) = @_;
2603
2604         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2605         return $e->die_event unless $e->checkauth;
2606         my $note = $e->retrieve_actor_usr_note($noteid)
2607                 or return $e->die_event;
2608         my $user = $e->retrieve_actor_user($note->usr)
2609                 or return $e->die_event;
2610         return $e->die_event unless 
2611                 $e->allowed('UPDATE_USER', $user->home_ou);
2612         
2613         $e->delete_actor_usr_note($note) or return $e->die_event;
2614         $e->commit;
2615         return 1;
2616 }
2617
2618
2619 __PACKAGE__->register_method (
2620         method          => 'update_user_note',
2621         api_name                => 'open-ils.actor.note.update',
2622         signature       => q/
2623                 @param authtoken The login session key
2624                 @param note The note
2625         /
2626 );
2627
2628 sub update_user_note {
2629         my( $self, $conn, $auth, $note ) = @_;
2630         my $e = new_editor(authtoken=>$auth, xact=>1);
2631         return $e->event unless $e->checkauth;
2632         my $patron = $e->retrieve_actor_user($note->usr)
2633                 or return $e->event;
2634         return $e->event unless 
2635                 $e->allowed('UPDATE_USER', $patron->home_ou);
2636         $e->update_actor_user_note($note)
2637                 or return $e->event;
2638         $e->commit;
2639         return 1;
2640 }
2641
2642
2643
2644
2645 __PACKAGE__->register_method (
2646         method          => 'create_closed_date',
2647         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2648         signature       => q/
2649                 Creates a new closing entry for the given org_unit
2650                 @param authtoken The login session key
2651                 @param note The closed_date object
2652         /
2653 );
2654 sub create_closed_date {
2655         my( $self, $conn, $authtoken, $cd ) = @_;
2656
2657         my( $user, $evt ) = $U->checkses($authtoken);
2658         return $evt if $evt;
2659
2660         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2661         return $evt if $evt;
2662
2663         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2664
2665         my $id = $U->storagereq(
2666                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2667         return $U->DB_UPDATE_FAILED($cd) unless $id;
2668         return $id;
2669 }
2670
2671
2672 __PACKAGE__->register_method (
2673         method          => 'delete_closed_date',
2674         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2675         signature       => q/
2676                 Deletes a closing entry for the given org_unit
2677                 @param authtoken The login session key
2678                 @param noteid The close_date id
2679         /
2680 );
2681 sub delete_closed_date {
2682         my( $self, $conn, $authtoken, $cd ) = @_;
2683
2684         my( $user, $evt ) = $U->checkses($authtoken);
2685         return $evt if $evt;
2686
2687         my $cd_obj;
2688         ($cd_obj, $evt) = fetch_closed_date($cd);
2689         return $evt if $evt;
2690
2691         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2692         return $evt if $evt;
2693
2694         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2695
2696         my $stat = $U->storagereq(
2697                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2698         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2699         return $stat;
2700 }
2701
2702
2703 __PACKAGE__->register_method(
2704         method => 'usrname_exists',
2705         api_name        => 'open-ils.actor.username.exists',
2706         signature => q/
2707                 Returns 1 if the requested username exists, returns 0 otherwise
2708         /
2709 );
2710
2711 sub usrname_exists {
2712         my( $self, $conn, $auth, $usrname ) = @_;
2713         my $e = new_editor(authtoken=>$auth);
2714         return $e->event unless $e->checkauth;
2715         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2716         return $$a[0] if $a and @$a;
2717         return 0;
2718 }
2719
2720 __PACKAGE__->register_method(
2721         method => 'barcode_exists',
2722         api_name        => 'open-ils.actor.barcode.exists',
2723         signature => q/
2724                 Returns 1 if the requested barcode exists, returns 0 otherwise
2725         /
2726 );
2727
2728 sub barcode_exists {
2729         my( $self, $conn, $auth, $barcode ) = @_;
2730         my $e = new_editor(authtoken=>$auth);
2731         return $e->event unless $e->checkauth;
2732         my $card = $e->search_actor_card({barcode => $barcode});
2733     return 0 unless @$card;
2734     return $card->[0]->usr;
2735 }
2736
2737
2738 __PACKAGE__->register_method(
2739         method => 'retrieve_net_levels',
2740         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2741 );
2742
2743 sub retrieve_net_levels {
2744         my( $self, $conn, $auth ) = @_;
2745         my $e = new_editor(authtoken=>$auth);
2746         return $e->event unless $e->checkauth;
2747         return $e->retrieve_all_config_net_access_level();
2748 }
2749
2750
2751 __PACKAGE__->register_method(
2752         method => 'fetch_org_by_shortname',
2753         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2754 );
2755 sub fetch_org_by_shortname {
2756         my( $self, $conn, $sname ) = @_;
2757         my $e = new_editor();
2758         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2759         return $e->event unless $org;
2760         return $org;
2761 }
2762
2763
2764 __PACKAGE__->register_method(
2765         method => 'session_home_lib',
2766         api_name => 'open-ils.actor.session.home_lib',
2767 );
2768
2769 sub session_home_lib {
2770         my( $self, $conn, $auth ) = @_;
2771         my $e = new_editor(authtoken=>$auth);
2772         return undef unless $e->checkauth;
2773         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2774         return $org->shortname;
2775 }
2776
2777 __PACKAGE__->register_method(
2778         method => 'session_safe_token',
2779         api_name => 'open-ils.actor.session.safe_token',
2780         signature => q/
2781                 Returns a hashed session ID that is safe for export to the world.
2782                 This safe token will expire after 1 hour of non-use.
2783                 @param auth Active authentication token
2784         /
2785 );
2786
2787 sub session_safe_token {
2788         my( $self, $conn, $auth ) = @_;
2789         my $e = new_editor(authtoken=>$auth);
2790         return undef unless $e->checkauth;
2791
2792         my $safe_token = md5_hex($auth);
2793
2794         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2795
2796         # Add more like the following if needed...
2797         $cache->put_cache(
2798                 "safe-token-home_lib-shortname-$safe_token",
2799                 $e->retrieve_actor_org_unit(
2800                         $e->requestor->home_ou
2801                 )->shortname,
2802                 60 * 60
2803         );
2804
2805         return $safe_token;
2806 }
2807
2808
2809 __PACKAGE__->register_method(
2810         method => 'safe_token_home_lib',
2811         api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2812         signature => q/
2813                 Returns the home library shortname from the session
2814                 asscociated with a safe token from generated by
2815                 open-ils.actor.session.safe_token.
2816                 @param safe_token Active safe token
2817         /
2818 );
2819
2820 sub safe_token_home_lib {
2821         my( $self, $conn, $safe_token ) = @_;
2822
2823         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2824         return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2825 }
2826
2827
2828
2829 __PACKAGE__->register_method(
2830         method => 'slim_tree',
2831         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2832 );
2833 sub slim_tree {
2834         my $tree = new_editor()->search_actor_org_unit( 
2835                 [
2836                         {"parent_ou" => undef },
2837                         {
2838                                 flesh                           => -1,
2839                                 flesh_fields    => { aou =>  ['children'] },
2840                                 order_by                        => { aou => 'name'},
2841                                 select                  => { aou => ["id","shortname", "name"]},
2842                         }
2843                 ]
2844         )->[0];
2845
2846         return trim_tree($tree);
2847 }
2848
2849
2850 sub trim_tree {
2851         my $tree = shift;
2852         return undef unless $tree;
2853         my $htree = {
2854                 code => $tree->shortname,
2855                 name => $tree->name,
2856         };
2857         if( $tree->children and @{$tree->children} ) {
2858                 $htree->{children} = [];
2859                 for my $c (@{$tree->children}) {
2860                         push( @{$htree->{children}}, trim_tree($c) );
2861                 }
2862         }
2863
2864         return $htree;
2865 }
2866
2867
2868 __PACKAGE__->register_method(
2869         method  => "update_penalties",
2870         api_name        => "open-ils.actor.user.penalties.update");
2871 sub update_penalties {
2872         my( $self, $conn, $auth, $userid ) = @_;
2873         my $e = new_editor(authtoken=>$auth);
2874         return $e->event unless $e->checkauth;
2875         $U->update_patron_penalties( 
2876                 authtoken => $auth,
2877                 patronid  => $userid,
2878         );
2879         return 1;
2880 }
2881
2882
2883
2884 __PACKAGE__->register_method(
2885         method  => "user_retrieve_fleshed_by_id",
2886         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2887
2888 sub user_retrieve_fleshed_by_id {
2889         my( $self, $client, $auth, $user_id, $fields ) = @_;
2890         my $e = new_editor(authtoken => $auth);
2891         return $e->event unless $e->checkauth;
2892
2893         if( $e->requestor->id != $user_id ) {
2894                 return $e->event unless $e->allowed('VIEW_USER');
2895         }
2896
2897         $fields ||= [
2898                 "cards",
2899                 "card",
2900                 "standing_penalties",
2901                 "addresses",
2902                 "billing_address",
2903                 "mailing_address",
2904                 "stat_cat_entries" ];
2905         return new_flesh_user($user_id, $fields, $e);
2906 }
2907
2908
2909 sub new_flesh_user {
2910
2911         my $id = shift;
2912         my $fields = shift || [];
2913         my $e   = shift || new_editor(xact=>1);
2914
2915         my $user = $e->retrieve_actor_user(
2916         [
2917         $id,
2918         {
2919                 "flesh"                         => 1,
2920                 "flesh_fields" =>  { "au" => $fields }
2921         }
2922         ]
2923         ) or return $e->event;
2924
2925
2926         if( grep { $_ eq 'addresses' } @$fields ) {
2927
2928                 $user->addresses([]) unless @{$user->addresses};
2929         
2930                 if( ref $user->billing_address ) {
2931                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2932                                 push( @{$user->addresses}, $user->billing_address );
2933                         }
2934                 }
2935         
2936                 if( ref $user->mailing_address ) {
2937                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2938                                 push( @{$user->addresses}, $user->mailing_address );
2939                         }
2940                 }
2941         }
2942
2943         $e->rollback;
2944         $user->clear_passwd();
2945         return $user;
2946 }
2947
2948
2949
2950
2951 __PACKAGE__->register_method(
2952         method  => "user_retrieve_parts",
2953         api_name        => "open-ils.actor.user.retrieve.parts",);
2954
2955 sub user_retrieve_parts {
2956         my( $self, $client, $auth, $user_id, $fields ) = @_;
2957         my $e = new_editor(authtoken => $auth);
2958         return $e->event unless $e->checkauth;
2959         if( $e->requestor->id != $user_id ) {
2960                 return $e->event unless $e->allowed('VIEW_USER');
2961         }
2962         my @resp;
2963         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2964         push(@resp, $user->$_()) for(@$fields);
2965         return \@resp;
2966 }
2967
2968
2969
2970 __PACKAGE__->register_method(
2971     method => 'user_opt_in_enabled',
2972     api_name => 'open-ils.actor.user.org_unit_opt_in.enabled',
2973     signature => q/
2974         @return 1 if user opt-in is globally enabled, 0 otherwise.
2975     /);
2976
2977 sub user_opt_in_enabled {
2978     my($self, $conn) = @_;
2979     my $sc = OpenSRF::Utils::SettingsClient->new;
2980     return 1 if lc($sc->config_value(share => user => 'opt_in')) eq 'true'; 
2981     return 0;
2982 }
2983     
2984
2985 __PACKAGE__->register_method(
2986     method => 'user_opt_in_at_org',
2987     api_name => 'open-ils.actor.user.org_unit_opt_in.check',
2988     signature => q/
2989         @param $auth The auth token
2990         @param user_id The ID of the user to test
2991         @return 1 if the user has opted in at the specified org,
2992             event on error, and 0 otherwise. /);
2993 sub user_opt_in_at_org {
2994     my($self, $conn, $auth, $user_id) = @_;
2995
2996     # see if we even need to enforce the opt-in value
2997     return 1 unless $self->user_opt_in_enabled;
2998
2999         my $e = new_editor(authtoken => $auth);
3000         return $e->event unless $e->checkauth;
3001     my $org_id = $e->requestor->ws_ou;
3002
3003     my $user = $e->retrieve_actor_user($user_id) or return $e->event;
3004         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);
3005
3006     # user is automatically opted-in at the home org
3007     return 1 if $user->home_ou eq $org_id;
3008
3009     my $vals = $e->search_actor_usr_org_unit_opt_in(
3010         {org_unit=>$org_id, usr=>$user_id},{idlist=>1});
3011
3012     return 1 if @$vals;
3013     return 0;
3014 }
3015
3016 __PACKAGE__->register_method(
3017     method => 'create_user_opt_in_at_org',
3018     api_name => 'open-ils.actor.user.org_unit_opt_in.create',
3019     signature => q/
3020         @param $auth The auth token
3021         @param user_id The ID of the user to test
3022         @return The ID of the newly created object, event on error./);
3023
3024 sub create_user_opt_in_at_org {
3025     my($self, $conn, $auth, $user_id) = @_;
3026
3027         my $e = new_editor(authtoken => $auth, xact=>1);
3028         return $e->die_event unless $e->checkauth;
3029     my $org_id = $e->requestor->ws_ou;
3030
3031     my $user = $e->retrieve_actor_user($user_id) or return $e->die_event;
3032         return $e->die_event unless $e->allowed('UPDATE_USER', $user->home_ou);
3033
3034     my $opt_in = Fieldmapper::actor::usr_org_unit_opt_in->new;
3035
3036     $opt_in->org_unit($org_id);
3037     $opt_in->usr($user_id);
3038     $opt_in->staff($e->requestor->id);
3039     $opt_in->opt_in_ts('now');
3040     $opt_in->opt_in_ws($e->requestor->wsid);
3041
3042     $opt_in = $e->create_actor_usr_org_unit_opt_in($opt_in)
3043         or return $e->die_event;
3044
3045     $e->commit;
3046
3047     return $opt_in->id;
3048 }
3049
3050
3051
3052
3053
3054
3055 1;
3056