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