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