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