]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
first cut at work_ou support in the user perm editor
[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 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} = 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 = 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 => 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 => 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                         JSON->perl2JSON($opatron). " |||| new patron = ".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_perms",
783         api_name        => "open-ils.actor.user.permissions.update",
784 );
785
786 sub set_user_perms {
787         my $self = shift;
788         my $client = shift;
789         my $ses = shift;
790         my $maps = shift;
791
792         my $session = $apputils->start_db_session();
793
794         my( $user_obj, $evt ) = $U->checkses($ses);
795         return $evt if $evt;
796
797         my $perms = $session->request('open-ils.storage.permission.user_perms.atomic', $user_obj->id)->gather(1);
798
799         my $all = undef;
800         $all = 1 if ($user_obj->super_user());
801     $all = 1 unless ($U->check_perms($user_obj->id, $user_obj->home_ou, 'EVERYTHING'));
802
803         for my $map (@$maps) {
804
805                 my $method = "open-ils.storage.direct.permission.usr_perm_map.update";
806                 if ($map->isdeleted()) {
807                         $method = "open-ils.storage.direct.permission.usr_perm_map.delete";
808                 } elsif ($map->isnew()) {
809                         $method = "open-ils.storage.direct.permission.usr_perm_map.create";
810                         $map->clear_id;
811                 }
812
813                 next if (!$all || !grep { $_->perm eq $map->perm and $_->grantable == 1 and $_->depth <= $map->depth } @$perms);
814
815                 #warn( "Updating permissions with method $method and session $ses and map $map" );
816                 $logger->info( "Updating permissions with method $method and map $map" );
817
818                 my $stat = $session->request($method, $map)->gather(1);
819                 $logger->warn( "update failed: ".$U->DB_UPDATE_FAILED($map) ) unless defined($stat);
820
821         }
822
823         $apputils->commit_db_session($session);
824
825         return scalar(@$maps);
826 }
827
828
829 sub _create_standing_penalties {
830
831         my($session, $user_session, $patron, $new_patron) = @_;
832
833         my $maps = $patron->standing_penalties;
834         my $method;
835
836         for my $map (@$maps) {
837
838                 if ($map->isdeleted()) {
839                         $method = "open-ils.storage.direct.actor.user_standing_penalty.delete";
840                 } elsif ($map->isnew()) {
841                         $method = "open-ils.storage.direct.actor.user_standing_penalty.create";
842                         $map->clear_id;
843                 } else {
844                         next;
845                 }
846
847                 $map->usr($new_patron->id);
848
849                 $logger->debug( "Updating standing penalty with method $method and session $user_session and map $map" );
850
851                 my $stat = $session->request($method, $map)->gather(1);
852                 return (undef, $U->DB_UPDATE_FAILED($map)) unless $stat;
853         }
854
855         return ($new_patron, undef);
856 }
857
858
859
860 __PACKAGE__->register_method(
861         method  => "search_username",
862         api_name        => "open-ils.actor.user.search.username",
863 );
864
865 sub search_username {
866         my($self, $client, $username) = @_;
867     return new_editor()->search_actor_user({usrname=>$username});
868 }
869
870
871
872
873 __PACKAGE__->register_method(
874         method  => "user_retrieve_by_barcode",
875         api_name        => "open-ils.actor.user.fleshed.retrieve_by_barcode",);
876
877 sub user_retrieve_by_barcode {
878         my($self, $client, $user_session, $barcode) = @_;
879
880         $logger->debug("Searching for user with barcode $barcode");
881         my ($user_obj, $evt) = $apputils->checkses($user_session);
882         return $evt if $evt;
883
884         my $card = OpenILS::Application::AppUtils->simple_scalar_request(
885                         "open-ils.cstore", 
886                         "open-ils.cstore.direct.actor.card.search.atomic",
887                         { barcode => $barcode }
888         );
889
890         if(!$card || !$card->[0]) {
891                 return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' );
892         }
893
894         $card = $card->[0];
895         my $user = flesh_user($card->usr());
896
897         $evt = $U->check_perms($user_obj->id, $user->home_ou, 'VIEW_USER');
898         return $evt if $evt;
899
900         if(!$user) { return OpenILS::Event->new( 'ACTOR_USER_NOT_FOUND' ); }
901         return $user;
902
903 }
904
905
906
907 __PACKAGE__->register_method(
908         method  => "get_user_by_id",
909         api_name        => "open-ils.actor.user.retrieve",);
910
911 sub get_user_by_id {
912         my ($self, $client, $auth, $id) = @_;
913         my $e = new_editor(authtoken=>$auth);
914         return $e->event unless $e->checkauth;
915         my $user = $e->retrieve_actor_user($id)
916                 or return $e->event;
917         return $e->event unless $e->allowed('VIEW_USER', $user->home_ou);       
918         return $user;
919 }
920
921
922
923 __PACKAGE__->register_method(
924         method  => "get_org_types",
925         api_name        => "open-ils.actor.org_types.retrieve",);
926
927 my $org_types;
928 sub get_org_types {
929         my($self, $client) = @_;
930         return $org_types if $org_types;
931         return $org_types = new_editor()->retrieve_all_actor_org_unit_type();
932 }
933
934
935
936 __PACKAGE__->register_method(
937         method  => "get_user_ident_types",
938         api_name        => "open-ils.actor.user.ident_types.retrieve",
939 );
940 my $ident_types;
941 sub get_user_ident_types {
942         return $ident_types if $ident_types;
943         return $ident_types = 
944                 new_editor()->retrieve_all_config_identification_type();
945 }
946
947
948
949
950 __PACKAGE__->register_method(
951         method  => "get_org_unit",
952         api_name        => "open-ils.actor.org_unit.retrieve",
953 );
954
955 sub get_org_unit {
956         my( $self, $client, $user_session, $org_id ) = @_;
957         my $e = new_editor(authtoken => $user_session);
958         if(!$org_id) {
959                 return $e->event unless $e->checkauth;
960                 $org_id = $e->requestor->ws_ou;
961         }
962         my $o = $e->retrieve_actor_org_unit($org_id)
963                 or return $e->event;
964         return $o;
965 }
966
967 __PACKAGE__->register_method(
968         method  => "search_org_unit",
969         api_name        => "open-ils.actor.org_unit_list.search",
970 );
971
972 sub search_org_unit {
973
974         my( $self, $client, $field, $value ) = @_;
975
976         my $list = OpenILS::Application::AppUtils->simple_scalar_request(
977                 "open-ils.cstore",
978                 "open-ils.cstore.direct.actor.org_unit.search.atomic", 
979                 { $field => $value } );
980
981         return $list;
982 }
983
984
985 # build the org tree
986
987 __PACKAGE__->register_method(
988         method  => "get_org_tree",
989         api_name        => "open-ils.actor.org_tree.retrieve",
990         argc            => 0, 
991         note            => "Returns the entire org tree structure",
992 );
993
994 sub get_org_tree {
995         my( $self, $client) = @_;
996
997         $cache  = OpenSRF::Utils::Cache->new("global", 0) unless $cache;
998         my $tree = $cache->get_cache('orgtree');
999         return $tree if $tree;
1000
1001         $tree = new_editor()->search_actor_org_unit( 
1002                 [
1003                         {"parent_ou" => undef },
1004                         {
1005                                 flesh                           => 2,
1006                                 flesh_fields    => { aou =>  ['children'] },
1007                                 order_by                        => { aou => 'name'}
1008                         }
1009                 ]
1010         )->[0];
1011
1012         $cache->put_cache('orgtree', $tree);
1013         return $tree;
1014 }
1015
1016
1017 # turns an org list into an org tree
1018 sub build_org_tree {
1019
1020         my( $self, $orglist) = @_;
1021
1022         return $orglist unless ref $orglist;
1023     return $$orglist[0] if @$orglist == 1;
1024
1025         my @list = sort { 
1026                 $a->ou_type <=> $b->ou_type ||
1027                 $a->name cmp $b->name } @$orglist;
1028
1029         for my $org (@list) {
1030
1031                 next unless ($org and defined($org->parent_ou));
1032                 my ($parent) = grep { $_->id == $org->parent_ou } @list;
1033                 next unless $parent;
1034
1035                 $parent->children([]) unless defined($parent->children); 
1036                 push( @{$parent->children}, $org );
1037         }
1038
1039         return $list[0];
1040
1041 }
1042
1043
1044 __PACKAGE__->register_method(
1045         method  => "get_org_descendants",
1046         api_name        => "open-ils.actor.org_tree.descendants.retrieve"
1047 );
1048
1049 # depth is optional.  org_unit is the id
1050 sub get_org_descendants {
1051         my( $self, $client, $org_unit, $depth ) = @_;
1052         my $orglist = $apputils->simple_scalar_request(
1053                         "open-ils.storage", 
1054                         "open-ils.storage.actor.org_unit.descendants.atomic",
1055                         $org_unit, $depth );
1056         return $self->build_org_tree($orglist);
1057 }
1058
1059
1060 __PACKAGE__->register_method(
1061         method  => "get_org_ancestors",
1062         api_name        => "open-ils.actor.org_tree.ancestors.retrieve"
1063 );
1064
1065 # depth is optional.  org_unit is the id
1066 sub get_org_ancestors {
1067         my( $self, $client, $org_unit, $depth ) = @_;
1068         my $orglist = $apputils->simple_scalar_request(
1069                         "open-ils.storage", 
1070                         "open-ils.storage.actor.org_unit.ancestors.atomic",
1071                         $org_unit, $depth );
1072         return $self->build_org_tree($orglist);
1073 }
1074
1075
1076 __PACKAGE__->register_method(
1077         method  => "get_standings",
1078         api_name        => "open-ils.actor.standings.retrieve"
1079 );
1080
1081 my $user_standings;
1082 sub get_standings {
1083         return $user_standings if $user_standings;
1084         return $user_standings = 
1085                 $apputils->simple_scalar_request(
1086                         "open-ils.cstore",
1087                         "open-ils.cstore.direct.config.standing.search.atomic",
1088                         { id => { "!=" => undef } }
1089                 );
1090 }
1091
1092
1093
1094 __PACKAGE__->register_method(
1095         method  => "get_my_org_path",
1096         api_name        => "open-ils.actor.org_unit.full_path.retrieve"
1097 );
1098
1099 sub get_my_org_path {
1100         my( $self, $client, $auth, $org_id ) = @_;
1101         my $e = new_editor(authtoken=>$auth);
1102         return $e->event unless $e->checkauth;
1103         $org_id = $e->requestor->ws_ou unless defined $org_id;
1104
1105         return $apputils->simple_scalar_request(
1106                 "open-ils.storage",
1107                 "open-ils.storage.actor.org_unit.full_path.atomic",
1108                 $org_id );
1109 }
1110
1111
1112 __PACKAGE__->register_method(
1113         method  => "patron_adv_search",
1114         api_name        => "open-ils.actor.patron.search.advanced" );
1115 sub patron_adv_search {
1116         my( $self, $client, $auth, $search_hash, $search_limit, $search_sort, $include_inactive ) = @_;
1117         my $e = new_editor(authtoken=>$auth);
1118         return $e->event unless $e->checkauth;
1119         return $e->event unless $e->allowed('VIEW_USER');
1120         return $U->storagereq(
1121                 "open-ils.storage.actor.user.crazy_search", 
1122                 $search_hash, $search_limit, $search_sort, $include_inactive);
1123 }
1124
1125
1126
1127 =head old
1128 sub _verify_password {
1129         my($user_session, $password) = @_;
1130         my $user_obj = $apputils->check_user_session($user_session); 
1131
1132         #grab the user with password
1133         $user_obj = $apputils->simple_scalar_request(
1134                 "open-ils.cstore", 
1135                 "open-ils.cstore.direct.actor.user.retrieve",
1136                 $user_obj->id );
1137
1138         if($user_obj->passwd eq $password) {
1139                 return 1;
1140         }
1141
1142         return 0;
1143 }
1144
1145
1146 __PACKAGE__->register_method(
1147         method  => "update_password",
1148         api_name        => "open-ils.actor.user.password.update");
1149
1150 __PACKAGE__->register_method(
1151         method  => "update_password",
1152         api_name        => "open-ils.actor.user.username.update");
1153
1154 __PACKAGE__->register_method(
1155         method  => "update_password",
1156         api_name        => "open-ils.actor.user.email.update");
1157
1158 sub update_password {
1159         my( $self, $client, $user_session, $new_value, $current_password ) = @_;
1160
1161         my $evt;
1162
1163         my $session = $apputils->start_db_session();
1164         my $user_obj = $apputils->check_user_session($user_session); 
1165
1166     #fetch the in-database version so we get the latest xact_id
1167     $user_obj = $session->request(
1168         'open-ils.storage.direct.actor.user.retrieve', $user_obj->id)->gather(1);
1169
1170         if($self->api_name =~ /password/o) {
1171
1172                 #make sure they know the current password
1173                 if(!_verify_password($user_session, md5_hex($current_password))) {
1174                         return OpenILS::Event->new('INCORRECT_PASSWORD');
1175                 }
1176
1177                 $logger->debug("update_password setting new password $new_value");
1178                 $user_obj->passwd($new_value);
1179
1180         } elsif($self->api_name =~ /username/o) {
1181                 my $users = search_username(undef, undef, $new_value); 
1182                 if( $users and $users->[0] ) {
1183                         return OpenILS::Event->new('USERNAME_EXISTS');
1184                 }
1185                 $user_obj->usrname($new_value);
1186
1187         } elsif($self->api_name =~ /email/o) {
1188                 #warn "Updating email to $new_value\n";
1189                 $user_obj->email($new_value);
1190         }
1191
1192
1193         ( $user_obj, $evt ) = _update_patron($session, $user_obj, $user_obj, 1);
1194         return $evt if $evt;
1195
1196         $apputils->commit_db_session($session);
1197
1198         if($user_obj) { return 1; }
1199         return undef;
1200 }
1201 =cut
1202
1203 __PACKAGE__->register_method(
1204         method  => "update_passwd",
1205         api_name        => "open-ils.actor.user.password.update");
1206
1207 __PACKAGE__->register_method(
1208         method  => "update_passwd",
1209         api_name        => "open-ils.actor.user.username.update");
1210
1211 __PACKAGE__->register_method(
1212         method  => "update_passwd",
1213         api_name        => "open-ils.actor.user.email.update");
1214
1215 sub update_passwd {
1216     my( $self, $conn, $auth, $new_val, $orig_pw ) = @_;
1217     my $e = new_editor(xact=>1, authtoken=>$auth);
1218     return $e->die_event unless $e->checkauth;
1219
1220     my $db_user = $e->retrieve_actor_user($e->requestor->id)
1221         or return $e->die_event;
1222     my $api = $self->api_name;
1223
1224     if( $api =~ /password/o ) {
1225
1226         # make sure the original password matches the in-database password
1227         return OpenILS::Event->new('INCORRECT_PASSWORD')
1228             if md5_hex($orig_pw) ne $db_user->passwd;
1229         $db_user->passwd($new_val);
1230
1231     } else {
1232
1233         # if we don't clear the password, the user will be updated with
1234         # a hashed version of the hashed version of their password
1235         $db_user->clear_passwd;
1236
1237         if( $api =~ /username/o ) {
1238
1239             # make sure no one else has this username
1240             my $exist = $e->search_actor_user({usrname=>$new_val},{idlist=>1}); 
1241                         return OpenILS::Event->new('USERNAME_EXISTS') if @$exist;
1242             $db_user->usrname($new_val);
1243
1244         } elsif( $api =~ /email/o ) {
1245             $db_user->email($new_val);
1246         }
1247     }
1248
1249     $e->update_actor_user($db_user) or return $e->die_event;
1250     $e->commit;
1251     return 1;
1252 }
1253
1254
1255
1256
1257 __PACKAGE__->register_method(
1258         method  => "check_user_perms",
1259         api_name        => "open-ils.actor.user.perm.check",
1260         notes           => <<"  NOTES");
1261         Takes a login session, user id, an org id, and an array of perm type strings.  For each
1262         perm type, if the user does *not* have the given permission it is added
1263         to a list which is returned from the method.  If all permissions
1264         are allowed, an empty list is returned
1265         if the logged in user does not match 'user_id', then the logged in user must
1266         have VIEW_PERMISSION priveleges.
1267         NOTES
1268
1269 sub check_user_perms {
1270         my( $self, $client, $login_session, $user_id, $org_id, $perm_types ) = @_;
1271
1272         my( $staff, $evt ) = $apputils->checkses($login_session);
1273         return $evt if $evt;
1274
1275         if($staff->id ne $user_id) {
1276                 if( $evt = $apputils->check_perms(
1277                         $staff->id, $org_id, 'VIEW_PERMISSION') ) {
1278                         return $evt;
1279                 }
1280         }
1281
1282         my @not_allowed;
1283         for my $perm (@$perm_types) {
1284                 if($apputils->check_perms($user_id, $org_id, $perm)) {
1285                         push @not_allowed, $perm;
1286                 }
1287         }
1288
1289         return \@not_allowed
1290 }
1291
1292 __PACKAGE__->register_method(
1293         method  => "check_user_perms2",
1294         api_name        => "open-ils.actor.user.perm.check.multi_org",
1295         notes           => q/
1296                 Checks the permissions on a list of perms and orgs for a user
1297                 @param authtoken The login session key
1298                 @param user_id The id of the user to check
1299                 @param orgs The array of org ids
1300                 @param perms The array of permission names
1301                 @return An array of  [ orgId, permissionName ] arrays that FAILED the check
1302                 if the logged in user does not match 'user_id', then the logged in user must
1303                 have VIEW_PERMISSION priveleges.
1304         /);
1305
1306 sub check_user_perms2 {
1307         my( $self, $client, $authtoken, $user_id, $orgs, $perms ) = @_;
1308
1309         my( $staff, $target, $evt ) = $apputils->checkses_requestor(
1310                 $authtoken, $user_id, 'VIEW_PERMISSION' );
1311         return $evt if $evt;
1312
1313         my @not_allowed;
1314         for my $org (@$orgs) {
1315                 for my $perm (@$perms) {
1316                         if($apputils->check_perms($user_id, $org, $perm)) {
1317                                 push @not_allowed, [ $org, $perm ];
1318                         }
1319                 }
1320         }
1321
1322         return \@not_allowed
1323 }
1324
1325
1326 __PACKAGE__->register_method(
1327         method => 'check_user_perms3',
1328         api_name        => 'open-ils.actor.user.perm.highest_org',
1329         notes           => q/
1330                 Returns the highest org unit id at which a user has a given permission
1331                 If the requestor does not match the target user, the requestor must have
1332                 'VIEW_PERMISSION' rights at the home org unit of the target user
1333                 @param authtoken The login session key
1334                 @param userid The id of the user in question
1335                 @param perm The permission to check
1336                 @return The org unit highest in the org tree within which the user has
1337                 the requested permission
1338         /);
1339
1340 sub check_user_perms3 {
1341         my( $self, $client, $authtoken, $userid, $perm ) = @_;
1342
1343         my( $staff, $target, $org, $evt );
1344
1345         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1346                 $authtoken, $userid, 'VIEW_PERMISSION' );
1347         return $evt if $evt;
1348
1349         my $tree = $self->get_org_tree();
1350         return _find_highest_perm_org( $perm, $userid, $target->ws_ou, $tree );
1351 }
1352
1353
1354 sub _find_highest_perm_org {
1355         my ( $perm, $userid, $start_org, $org_tree ) = @_;
1356         my $org = $apputils->find_org($org_tree, $start_org );
1357
1358         my $lastid = -1;
1359         while( $org ) {
1360                 last if ($apputils->check_perms( $userid, $org->id, $perm )); # perm failed
1361                 $lastid = $org->id;
1362                 $org = $apputils->find_org( $org_tree, $org->parent_ou() );
1363         }
1364
1365         return $lastid;
1366 }
1367
1368 __PACKAGE__->register_method(
1369         method => 'check_user_perms4',
1370         api_name        => 'open-ils.actor.user.perm.highest_org.batch',
1371         notes           => q/
1372                 Returns the highest org unit id at which a user has a given permission
1373                 If the requestor does not match the target user, the requestor must have
1374                 'VIEW_PERMISSION' rights at the home org unit of the target user
1375                 @param authtoken The login session key
1376                 @param userid The id of the user in question
1377                 @param perms An array of perm names to check 
1378                 @return An array of orgId's  representing the org unit 
1379                 highest in the org tree within which the user has the requested permission
1380                 The arrah of orgId's has matches the order of the perms array
1381         /);
1382
1383 sub check_user_perms4 {
1384         my( $self, $client, $authtoken, $userid, $perms ) = @_;
1385         
1386         my( $staff, $target, $org, $evt );
1387
1388         ( $staff, $target, $evt ) = $apputils->checkses_requestor(
1389                 $authtoken, $userid, 'VIEW_PERMISSION' );
1390         return $evt if $evt;
1391
1392         my @arr;
1393         return [] unless ref($perms);
1394         my $tree = $self->get_org_tree();
1395
1396         for my $p (@$perms) {
1397                 push( @arr, _find_highest_perm_org( $p, $userid, $target->home_ou, $tree ) );
1398         }
1399         return \@arr;
1400 }
1401
1402
1403
1404
1405 __PACKAGE__->register_method(
1406         method  => "user_fines_summary",
1407         api_name        => "open-ils.actor.user.fines.summary",
1408         notes           => <<"  NOTES");
1409         Returns a short summary of the users total open fines, excluding voided fines
1410         Params are login_session, user_id
1411         Returns a 'mous' object.
1412         NOTES
1413
1414 sub user_fines_summary {
1415         my( $self, $client, $auth, $user_id ) = @_;
1416         my $e = new_editor(authtoken=>$auth);
1417         return $e->event unless $e->checkauth;
1418         my $user = $e->retrieve_actor_user($user_id)
1419                 or return $e->event;
1420
1421         if( $user_id ne $e->requestor->id ) {
1422                 return $e->event unless 
1423                         $e->allowed('VIEW_USER_FINES_SUMMARY', $user->home_ou);
1424         }
1425         
1426         # run this inside a transaction to prevent replication delay errors
1427         my $ses = $U->start_db_session();
1428         my $s = $ses->request(
1429                 'open-ils.storage.money.open_user_summary.search', $user_id )->gather(1);
1430         $U->rollback_db_session($ses);
1431         return $s;
1432 }
1433
1434
1435
1436
1437 __PACKAGE__->register_method(
1438         method  => "user_transactions",
1439         api_name        => "open-ils.actor.user.transactions",
1440         notes           => <<"  NOTES");
1441         Returns a list of open user transactions (mbts objects);
1442         Params are login_session, user_id
1443         Optional third parameter is the transactions type.  defaults to all
1444         NOTES
1445
1446 __PACKAGE__->register_method(
1447         method  => "user_transactions",
1448         api_name        => "open-ils.actor.user.transactions.have_charge",
1449         notes           => <<"  NOTES");
1450         Returns a list of all open user transactions (mbts objects) that have an initial charge
1451         Params are login_session, user_id
1452         Optional third parameter is the transactions type.  defaults to all
1453         NOTES
1454
1455 __PACKAGE__->register_method(
1456         method  => "user_transactions",
1457         api_name        => "open-ils.actor.user.transactions.have_balance",
1458         notes           => <<"  NOTES");
1459         Returns a list of all open user transactions (mbts objects) that have a balance
1460         Params are login_session, user_id
1461         Optional third parameter is the transactions type.  defaults to all
1462         NOTES
1463
1464 __PACKAGE__->register_method(
1465         method  => "user_transactions",
1466         api_name        => "open-ils.actor.user.transactions.fleshed",
1467         notes           => <<"  NOTES");
1468         Returns an object/hash of transaction, circ, title where transaction = an open 
1469         user transactions (mbts objects), circ is the attached circluation, and title
1470         is the title the circ points to
1471         Params are login_session, user_id
1472         Optional third parameter is the transactions type.  defaults to all
1473         NOTES
1474
1475 __PACKAGE__->register_method(
1476         method  => "user_transactions",
1477         api_name        => "open-ils.actor.user.transactions.have_charge.fleshed",
1478         notes           => <<"  NOTES");
1479         Returns an object/hash of transaction, circ, title where transaction = an open 
1480         user transactions that has an initial charge (mbts objects), circ is the 
1481         attached circluation, and title is the title the circ points to
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_balance.fleshed",
1489         notes           => <<"  NOTES");
1490         Returns an object/hash of transaction, circ, title where transaction = an open 
1491         user transaction that has a balance (mbts objects), circ is the attached 
1492         circluation, and title is the title the circ points to
1493         Params are login_session, user_id
1494         Optional third parameter is the transaction type.  defaults to all
1495         NOTES
1496
1497 __PACKAGE__->register_method(
1498         method  => "user_transactions",
1499         api_name        => "open-ils.actor.user.transactions.count",
1500         notes           => <<"  NOTES");
1501         Returns an object/hash of transaction, circ, title where transaction = an open 
1502         user transactions (mbts objects), circ is the attached circluation, and title
1503         is the title the circ points to
1504         Params are login_session, user_id
1505         Optional third parameter is the transactions type.  defaults to all
1506         NOTES
1507
1508 __PACKAGE__->register_method(
1509         method  => "user_transactions",
1510         api_name        => "open-ils.actor.user.transactions.have_charge.count",
1511         notes           => <<"  NOTES");
1512         Returns an object/hash of transaction, circ, title where transaction = an open 
1513         user transactions that has an initial charge (mbts objects), circ is the 
1514         attached circluation, and title is the title the circ points to
1515         Params are login_session, user_id
1516         Optional third parameter is the transactions type.  defaults to all
1517         NOTES
1518
1519 __PACKAGE__->register_method(
1520         method  => "user_transactions",
1521         api_name        => "open-ils.actor.user.transactions.have_balance.count",
1522         notes           => <<"  NOTES");
1523         Returns an object/hash of transaction, circ, title where transaction = an open 
1524         user transaction that has a balance (mbts objects), circ is the attached 
1525         circluation, and title is the title the circ points to
1526         Params are login_session, user_id
1527         Optional third parameter is the transaction type.  defaults to all
1528         NOTES
1529
1530 __PACKAGE__->register_method(
1531         method  => "user_transactions",
1532         api_name        => "open-ils.actor.user.transactions.have_balance.total",
1533         notes           => <<"  NOTES");
1534         Returns an object/hash of transaction, circ, title where transaction = an open 
1535         user transaction that has a balance (mbts objects), circ is the attached 
1536         circluation, and title is the title the circ points to
1537         Params are login_session, user_id
1538         Optional third parameter is the transaction type.  defaults to all
1539         NOTES
1540
1541
1542
1543 sub user_transactions {
1544         my( $self, $client, $login_session, $user_id, $type ) = @_;
1545
1546         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1547                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
1548         return $evt if $evt;
1549
1550         my $api = $self->api_name();
1551         my $trans;
1552         my @xact;
1553
1554         if(defined($type)) { @xact = (xact_type =>  $type); 
1555
1556         } else { @xact = (); }
1557
1558         ($trans) = $self
1559                 ->method_lookup('open-ils.actor.user.transactions.history.still_open')
1560                 ->run($login_session => $user_id => $type);
1561
1562         if($api =~ /have_charge/o) {
1563
1564                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1565
1566         } elsif($api =~ /have_balance/o) {
1567
1568                 $trans = [ grep { int($_->balance_owed * 100) != 0 } @$trans ];
1569         } else {
1570
1571                 $trans = [ grep { int($_->total_owed * 100) > 0 } @$trans ];
1572
1573         }
1574
1575         if($api =~ /total/o) { 
1576                 my $total = 0.0;
1577                 for my $t (@$trans) {
1578                         $total += $t->balance_owed;
1579                 }
1580
1581                 $logger->debug("Total balance owed by user $user_id: $total");
1582                 return $total;
1583         }
1584
1585         if($api =~ /count/o) { return scalar @$trans; }
1586         if($api !~ /fleshed/o) { return $trans; }
1587
1588         my @resp;
1589         for my $t (@$trans) {
1590                         
1591                 if( $t->xact_type ne 'circulation' ) {
1592                         push @resp, {transaction => $t};
1593                         next;
1594                 }
1595
1596                 my $circ = $apputils->simple_scalar_request(
1597                                 "open-ils.cstore",
1598                                 "open-ils.cstore.direct.action.circulation.retrieve",
1599                                 $t->id );
1600
1601                 next unless $circ;
1602
1603                 my $title = $apputils->simple_scalar_request(
1604                         "open-ils.storage", 
1605                         "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1606                         $circ->target_copy );
1607
1608                 next unless $title;
1609
1610                 my $u = OpenILS::Utils::ModsParser->new();
1611                 $u->start_mods_batch($title->marc());
1612                 my $mods = $u->finish_mods_batch();
1613                 $mods->doc_id($title->id) if $mods;
1614
1615                 push @resp, {transaction => $t, circ => $circ, record => $mods };
1616
1617         }
1618
1619         return \@resp; 
1620
1621
1622
1623 __PACKAGE__->register_method(
1624         method  => "user_transaction_retrieve",
1625         api_name        => "open-ils.actor.user.transaction.fleshed.retrieve",
1626         argc            => 1,
1627         notes           => <<"  NOTES");
1628         Returns a fleshedtransaction record
1629         NOTES
1630 __PACKAGE__->register_method(
1631         method  => "user_transaction_retrieve",
1632         api_name        => "open-ils.actor.user.transaction.retrieve",
1633         argc            => 1,
1634         notes           => <<"  NOTES");
1635         Returns a transaction record
1636         NOTES
1637 sub user_transaction_retrieve {
1638         my( $self, $client, $login_session, $bill_id ) = @_;
1639
1640         # XXX I think I'm deprecated... make sure
1641
1642         my $trans = $apputils->simple_scalar_request( 
1643                 "open-ils.cstore",
1644                 "open-ils.cstore.direct.money.billable_transaction_summary.retrieve",
1645                 $bill_id
1646         );
1647
1648         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1649                 $login_session, $trans->usr, 'VIEW_USER_TRANSACTIONS' );
1650         return $evt if $evt;
1651         
1652         my $api = $self->api_name();
1653         if($api !~ /fleshed/o) { return $trans; }
1654
1655         if( $trans->xact_type ne 'circulation' ) {
1656                 $logger->debug("Returning non-circ transaction");
1657                 return {transaction => $trans};
1658         }
1659
1660         my $circ = $apputils->simple_scalar_request(
1661                         "open-ils.cstore",
1662                         "open-ils..direct.action.circulation.retrieve",
1663                         $trans->id );
1664
1665         return {transaction => $trans} unless $circ;
1666         $logger->debug("Found the circ transaction");
1667
1668         my $title = $apputils->simple_scalar_request(
1669                 "open-ils.storage", 
1670                 "open-ils.storage.fleshed.biblio.record_entry.retrieve_by_copy",
1671                 $circ->target_copy );
1672
1673         return {transaction => $trans, circ => $circ } unless $title;
1674         $logger->debug("Found the circ title");
1675
1676         my $mods;
1677         try {
1678                 my $u = OpenILS::Utils::ModsParser->new();
1679                 $u->start_mods_batch($title->marc());
1680                 $mods = $u->finish_mods_batch();
1681         } otherwise {
1682                 if ($title->id == OILS_PRECAT_RECORD) {
1683                         my $copy = $apputils->simple_scalar_request(
1684                                 "open-ils.cstore",
1685                                 "open-ils.cstore.direct.asset.copy.retrieve",
1686                                 $circ->target_copy );
1687
1688                         $mods = new Fieldmapper::metabib::virtual_record;
1689                         $mods->doc_id(OILS_PRECAT_RECORD);
1690                         $mods->title($copy->dummy_title);
1691                         $mods->author($copy->dummy_author);
1692                 }
1693         };
1694
1695         $logger->debug("MODSized the circ title");
1696
1697         return {transaction => $trans, circ => $circ, record => $mods };
1698 }
1699
1700
1701 __PACKAGE__->register_method(
1702         method  => "hold_request_count",
1703         api_name        => "open-ils.actor.user.hold_requests.count",
1704         argc            => 1,
1705         notes           => <<"  NOTES");
1706         Returns hold ready/total counts
1707         NOTES
1708 sub hold_request_count {
1709         my( $self, $client, $login_session, $userid ) = @_;
1710
1711         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1712                 $login_session, $userid, 'VIEW_HOLD' );
1713         return $evt if $evt;
1714         
1715
1716         my $holds = $apputils->simple_scalar_request(
1717                         "open-ils.cstore",
1718                         "open-ils.cstore.direct.action.hold_request.search.atomic",
1719                         { 
1720                                 usr => $userid,
1721                                 fulfillment_time => {"=" => undef },
1722                                 cancel_time => undef,
1723                         }
1724         );
1725
1726         my @ready;
1727         for my $h (@$holds) {
1728                 next unless $h->capture_time and $h->current_copy;
1729
1730                 my $copy = $apputils->simple_scalar_request(
1731                         "open-ils.cstore",
1732                         "open-ils.cstore.direct.asset.copy.retrieve",
1733                         $h->current_copy
1734                 );
1735
1736                 if ($copy and $copy->status == 8) {
1737                         push @ready, $h;
1738                 }
1739         }
1740
1741         return { total => scalar(@$holds), ready => scalar(@ready) };
1742 }
1743
1744
1745 __PACKAGE__->register_method(
1746         method  => "checkedout_count",
1747         api_name        => "open-ils.actor.user.checked_out.count__",
1748         argc            => 1,
1749         notes           => <<"  NOTES");
1750         Returns a transaction record
1751         NOTES
1752
1753 # XXX Deprecate Me
1754 sub checkedout_count {
1755         my( $self, $client, $login_session, $userid ) = @_;
1756
1757         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
1758                 $login_session, $userid, 'VIEW_CIRCULATIONS' );
1759         return $evt if $evt;
1760         
1761         my $circs = $apputils->simple_scalar_request(
1762                         "open-ils.cstore",
1763                         "open-ils.cstore.direct.action.circulation.search.atomic",
1764                         { usr => $userid, stop_fines => undef }
1765                         #{ usr => $userid, checkin_time => {"=" => undef } }
1766         );
1767
1768         my $parser = DateTime::Format::ISO8601->new;
1769
1770         my (@out,@overdue);
1771         for my $c (@$circs) {
1772                 my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1773                 my $due = $due_dt->epoch;
1774
1775                 if ($due < DateTime->today->epoch) {
1776                         push @overdue, $c;
1777                 }
1778         }
1779
1780         return { total => scalar(@$circs), overdue => scalar(@overdue) };
1781 }
1782
1783
1784 __PACKAGE__->register_method(
1785         method          => "checked_out",
1786         api_name                => "open-ils.actor.user.checked_out",
1787         argc                    => 2,
1788         signature       => q/
1789                 Returns a structure of circulations objects sorted by
1790                 out, overdue, lost, claims_returned, long_overdue.
1791                 A list of IDs are returned of each type.
1792                 lost, long_overdue, and claims_returned circ will not
1793                 be "finished" (there is an outstanding balance or some 
1794                 other pending action on the circ). 
1795
1796                 The .count method also includes a 'total' field which 
1797                 sums all "open" circs
1798         /
1799 );
1800
1801 __PACKAGE__->register_method(
1802         method          => "checked_out",
1803         api_name                => "open-ils.actor.user.checked_out.count",
1804         argc                    => 2,
1805         signature       => q/@see open-ils.actor.user.checked_out/
1806 );
1807
1808 sub checked_out {
1809         my( $self, $conn, $auth, $userid ) = @_;
1810
1811         my $e = new_editor(authtoken=>$auth);
1812         return $e->event unless $e->checkauth;
1813
1814         if( $userid ne $e->requestor->id ) {
1815                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1816         }
1817
1818         my $count = $self->api_name =~ /count/;
1819         return _checked_out( $count, $e, $userid );
1820 }
1821
1822 sub _checked_out {
1823         my( $iscount, $e, $userid ) = @_;
1824
1825
1826         my $meth = 'open-ils.storage.actor.user.checked_out';
1827         $meth = "$meth.count" if $iscount;
1828         return $U->storagereq($meth, $userid);
1829
1830 # XXX Old code - moved to storage
1831 #------------------------------------------------------------------------------
1832 #------------------------------------------------------------------------------
1833         my $circs = $e->search_action_circulation( 
1834                 { usr => $userid, checkin_time => undef });
1835
1836         my $parser = DateTime::Format::ISO8601->new;
1837
1838         # split the circs up into overdue and not-overdue circs
1839         my (@out,@overdue);
1840         for my $c (@$circs) {
1841                 if( $c->due_date ) {
1842                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1843                         my $due = $due_dt->epoch;
1844                         if ($due < DateTime->today->epoch) {
1845                                 push @overdue, $c;
1846                         } else {
1847                                 push @out, $c;
1848                         }
1849                 } else {
1850                         push @out, $c;
1851                 }
1852         }
1853
1854         my( @open, @od, @lost, @cr, @lo );
1855
1856         while (my $c = shift(@out)) {
1857                 push( @open, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1858                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1859                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1860                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1861         }
1862
1863         while (my $c = shift(@overdue)) {
1864                 push( @od, $c->id ) if (!$c->stop_fines || $c->stop_fines eq 'MAXFINES' || $c->stop_fines eq 'RENEW');
1865                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1866                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1867                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1868         }
1869
1870         if( $iscount ) {
1871                 return {
1872                         total           => @open + @od + @lost + @cr + @lo,
1873                         out             => scalar(@open),
1874                         overdue => scalar(@od),
1875                         lost            => scalar(@lost),
1876                         claims_returned => scalar(@cr),
1877                         long_overdue            => scalar(@lo)
1878                 };
1879         }
1880
1881         return {
1882                 out             => \@open,
1883                 overdue => \@od,
1884                 lost            => \@lost,
1885                 claims_returned => \@cr,
1886                 long_overdue            => \@lo
1887         };
1888 }
1889
1890
1891 sub _checked_out_WHAT {
1892         my( $iscount, $e, $userid ) = @_;
1893
1894         my $circs = $e->search_action_circulation( 
1895                 { usr => $userid, stop_fines => undef });
1896
1897         my $mcircs = $e->search_action_circulation( 
1898                 { 
1899                         usr => $userid, 
1900                         checkin_time => undef, 
1901                         xact_finish => undef, 
1902                 });
1903
1904         
1905         push( @$circs, @$mcircs );
1906
1907         my $parser = DateTime::Format::ISO8601->new;
1908
1909         # split the circs up into overdue and not-overdue circs
1910         my (@out,@overdue);
1911         for my $c (@$circs) {
1912                 if( $c->due_date ) {
1913                         my $due_dt = $parser->parse_datetime( clense_ISO8601( $c->due_date ) );
1914                         my $due = $due_dt->epoch;
1915                         if ($due < DateTime->today->epoch) {
1916                                 push @overdue, $c->id;
1917                         } else {
1918                                 push @out, $c->id;
1919                         }
1920                 } else {
1921                         push @out, $c->id;
1922                 }
1923         }
1924
1925         # grab all of the lost, claims-returned, and longoverdue circs
1926         #my $open = $e->search_action_circulation(
1927         #       {usr => $userid, stop_fines => { '!=' => undef }, xact_finish => undef });
1928
1929
1930         # these items have stop_fines, but no xact_finish, so money
1931         # is owed on them and they have not been checked in
1932         my $open = $e->search_action_circulation(
1933                 {
1934                         usr                             => $userid, 
1935                         stop_fines              => { in => [ qw/LOST CLAIMSRETURNED LONGOVERDUE/ ] }, 
1936                         xact_finish             => undef,
1937                         checkin_time    => undef,
1938                 }
1939         );
1940
1941
1942         my( @lost, @cr, @lo );
1943         for my $c (@$open) {
1944                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
1945                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
1946                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
1947         }
1948
1949
1950         if( $iscount ) {
1951                 return {
1952                         total           => @$circs + @lost + @cr + @lo,
1953                         out             => scalar(@out),
1954                         overdue => scalar(@overdue),
1955                         lost            => scalar(@lost),
1956                         claims_returned => scalar(@cr),
1957                         long_overdue            => scalar(@lo)
1958                 };
1959         }
1960
1961         return {
1962                 out             => \@out,
1963                 overdue => \@overdue,
1964                 lost            => \@lost,
1965                 claims_returned => \@cr,
1966                 long_overdue            => \@lo
1967         };
1968 }
1969
1970
1971
1972 __PACKAGE__->register_method(
1973         method          => "checked_in_with_fines",
1974         api_name                => "open-ils.actor.user.checked_in_with_fines",
1975         argc                    => 2,
1976         signature       => q/@see open-ils.actor.user.checked_out/
1977 );
1978 sub checked_in_with_fines {
1979         my( $self, $conn, $auth, $userid ) = @_;
1980
1981         my $e = new_editor(authtoken=>$auth);
1982         return $e->event unless $e->checkauth;
1983
1984         if( $userid ne $e->requestor->id ) {
1985                 return $e->event unless $e->allowed('VIEW_CIRCULATIONS');
1986         }
1987
1988         # money is owed on these items and they are checked in
1989         my $open = $e->search_action_circulation(
1990                 {
1991                         usr                             => $userid, 
1992                         xact_finish             => undef,
1993                         checkin_time    => { "!=" => undef },
1994                 }
1995         );
1996
1997
1998         my( @lost, @cr, @lo );
1999         for my $c (@$open) {
2000                 push( @lost, $c->id ) if $c->stop_fines eq 'LOST';
2001                 push( @cr, $c->id ) if $c->stop_fines eq 'CLAIMSRETURNED';
2002                 push( @lo, $c->id ) if $c->stop_fines eq 'LONGOVERDUE';
2003         }
2004
2005         return {
2006                 lost            => \@lost,
2007                 claims_returned => \@cr,
2008                 long_overdue            => \@lo
2009         };
2010 }
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020 __PACKAGE__->register_method(
2021         method  => "user_transaction_history",
2022         api_name        => "open-ils.actor.user.transactions.history",
2023         argc            => 1,
2024         notes           => <<"  NOTES");
2025         Returns a list of billable transaction ids for a user, optionally by type
2026         NOTES
2027 __PACKAGE__->register_method(
2028         method  => "user_transaction_history",
2029         api_name        => "open-ils.actor.user.transactions.history.have_charge",
2030         argc            => 1,
2031         notes           => <<"  NOTES");
2032         Returns a list of billable transaction ids for a user that have an initial charge, optionally by type
2033         NOTES
2034 __PACKAGE__->register_method(
2035         method  => "user_transaction_history",
2036         api_name        => "open-ils.actor.user.transactions.history.have_balance",
2037         argc            => 1,
2038         notes           => <<"  NOTES");
2039         Returns a list of billable transaction ids for a user that have a balance, optionally by type
2040         NOTES
2041 __PACKAGE__->register_method(
2042         method  => "user_transaction_history",
2043         api_name        => "open-ils.actor.user.transactions.history.still_open",
2044         argc            => 1,
2045         notes           => <<"  NOTES");
2046         Returns a list of billable transaction ids for a user that are not finished
2047         NOTES
2048 __PACKAGE__->register_method(
2049         method  => "user_transaction_history",
2050         api_name        => "open-ils.actor.user.transactions.history.have_bill",
2051         argc            => 1,
2052         notes           => <<"  NOTES");
2053         Returns a list of billable transaction ids for a user that has billings
2054         NOTES
2055
2056
2057
2058 =head old
2059 sub _user_transaction_history {
2060         my( $self, $client, $login_session, $user_id, $type ) = @_;
2061
2062         my( $user_obj, $target, $evt ) = $apputils->checkses_requestor(
2063                 $login_session, $user_id, 'VIEW_USER_TRANSACTIONS' );
2064         return $evt if $evt;
2065
2066         my $api = $self->api_name();
2067         my @xact;
2068         my @charge;
2069         my @balance;
2070
2071         @xact = (xact_type =>  $type) if(defined($type));
2072         @balance = (balance_owed => { "!=" => 0}) if($api =~ /have_balance/);
2073         @charge  = (last_billing_ts => { "<>" => undef }) if $api =~ /have_charge/;
2074
2075         $logger->debug("searching for transaction history: @xact : @balance, @charge");
2076
2077         my $trans = $apputils->simple_scalar_request( 
2078                 "open-ils.cstore",
2079                 "open-ils.cstore.direct.money.billable_transaction_summary.search.atomic",
2080                 { usr => $user_id, @xact, @charge, @balance }, { order_by => { mbts => 'xact_start DESC' } });
2081
2082         return [ map { $_->id } @$trans ];
2083 }
2084 =cut
2085
2086 =head SEE APPUTILS.PM
2087 sub _make_mbts {
2088         my @xacts = @_;
2089
2090         my @mbts;
2091         for my $x (@xacts) {
2092                 my $s = new Fieldmapper::money::billable_transaction_summary;
2093                 $s->id( $x->id );
2094                 $s->usr( $x->usr );
2095                 $s->xact_start( $x->xact_start );
2096                 $s->xact_finish( $x->xact_finish );
2097
2098                 my $to = 0;
2099                 my $lb = undef;
2100                 for my $b (@{ $x->billings }) {
2101                         next if ($U->is_true($b->voided));
2102                         $to += ($b->amount * 100);
2103                         $lb ||= $b->billing_ts;
2104                         if ($b->billing_ts ge $lb) {
2105                                 $lb = $b->billing_ts;
2106                                 $s->last_billing_note($b->note);
2107                                 $s->last_billing_ts($b->billing_ts);
2108                                 $s->last_billing_type($b->billing_type);
2109                         }
2110                 }
2111
2112                 $s->total_owed( sprintf('%0.2f', $to / 100 ) );
2113
2114                 my $tp = 0;
2115                 my $lp = undef;
2116                 for my $p (@{ $x->payments }) {
2117                         next if ($U->is_true($p->voided));
2118                         $tp += ($p->amount * 100);
2119                         $lp ||= $p->payment_ts;
2120                         if ($p->payment_ts ge $lp) {
2121                                 $lp = $p->payment_ts;
2122                                 $s->last_payment_note($p->note);
2123                                 $s->last_payment_ts($p->payment_ts);
2124                                 $s->last_payment_type($p->payment_type);
2125                         }
2126                 }
2127                 $s->total_paid( sprintf('%0.2f', $tp / 100 ) );
2128
2129                 $s->balance_owed( sprintf('%0.2f', ($to - $tp) / 100) );
2130
2131                 $s->xact_type( 'grocery' ) if ($x->grocery);
2132                 $s->xact_type( 'circulation' ) if ($x->circulation);
2133
2134                 push @mbts, $s;
2135         }
2136
2137         return @mbts;
2138 }
2139 =cut
2140
2141 sub user_transaction_history {
2142         my( $self, $conn, $auth, $userid, $type ) = @_;
2143
2144         # run inside of a transaction to prevent replication delays
2145         my $e = new_editor(xact=>1, authtoken=>$auth);
2146         return $e->die_event unless $e->checkauth;
2147
2148         if( $e->requestor->id ne $userid ) {
2149                 return $e->die_event 
2150                         unless $e->allowed('VIEW_USER_TRANSACTIONS');
2151         }
2152
2153         my $api = $self->api_name;
2154         my @xact_finish  = (xact_finish => undef ) if ($api =~ /history.still_open$/);
2155
2156         my @xacts = @{ $e->search_money_billable_transaction(
2157                 [       { usr => $userid, @xact_finish },
2158                         { flesh => 1,
2159                           flesh_fields => { mbt => [ qw/billings payments grocery circulation/ ] },
2160                           order_by => { mbt => 'xact_start DESC' },
2161                         }
2162                 ],
2163       {substream => 1}
2164         ) };
2165
2166         $e->rollback;
2167
2168         #my @mbts = _make_mbts( @xacts );
2169         my @mbts = $U->make_mbts( @xacts );
2170
2171         if(defined($type)) {
2172                 @mbts = grep { $_->xact_type eq $type } @mbts;
2173         }
2174
2175         if($api =~ /have_balance/o) {
2176                 @mbts = grep { int($_->balance_owed * 100) != 0 } @mbts;
2177         }
2178
2179         if($api =~ /have_charge/o) {
2180                 @mbts = grep { defined($_->last_billing_ts) } @mbts;
2181         }
2182
2183         if($api =~ /have_bill/o) {
2184                 @mbts = grep { int($_->total_owed * 100) != 0 } @mbts;
2185         }
2186
2187         return [@mbts];
2188 }
2189
2190
2191
2192 __PACKAGE__->register_method(
2193         method  => "user_perms",
2194         api_name        => "open-ils.actor.permissions.user_perms.retrieve",
2195         argc            => 1,
2196         notes           => <<"  NOTES");
2197         Returns a list of permissions
2198         NOTES
2199 sub user_perms {
2200         my( $self, $client, $authtoken, $user ) = @_;
2201
2202         my( $staff, $evt ) = $apputils->checkses($authtoken);
2203         return $evt if $evt;
2204
2205         $user ||= $staff->id;
2206
2207         if( $user != $staff->id and $evt = $apputils->check_perms( $staff->id, $staff->home_ou, 'VIEW_PERMISSION') ) {
2208                 return $evt;
2209         }
2210
2211         return $apputils->simple_scalar_request(
2212                 "open-ils.storage",
2213                 "open-ils.storage.permission.user_perms.atomic",
2214                 $user);
2215 }
2216
2217 __PACKAGE__->register_method(
2218         method  => "retrieve_perms",
2219         api_name        => "open-ils.actor.permissions.retrieve",
2220         notes           => <<"  NOTES");
2221         Returns a list of permissions
2222         NOTES
2223 sub retrieve_perms {
2224         my( $self, $client ) = @_;
2225         return $apputils->simple_scalar_request(
2226                 "open-ils.cstore",
2227                 "open-ils.cstore.direct.permission.perm_list.search.atomic",
2228                 { id => { '!=' => undef } }
2229         );
2230 }
2231
2232 __PACKAGE__->register_method(
2233         method  => "retrieve_groups",
2234         api_name        => "open-ils.actor.groups.retrieve",
2235         notes           => <<"  NOTES");
2236         Returns a list of user groupss
2237         NOTES
2238 sub retrieve_groups {
2239         my( $self, $client ) = @_;
2240         return new_editor()->retrieve_all_permission_grp_tree();
2241 }
2242
2243 __PACKAGE__->register_method(
2244         method  => "retrieve_org_address",
2245         api_name        => "open-ils.actor.org_unit.address.retrieve",
2246         notes           => <<'  NOTES');
2247         Returns an org_unit address by ID
2248         @param An org_address ID
2249         NOTES
2250 sub retrieve_org_address {
2251         my( $self, $client, $id ) = @_;
2252         return $apputils->simple_scalar_request(
2253                 "open-ils.cstore",
2254                 "open-ils.cstore.direct.actor.org_address.retrieve",
2255                 $id
2256         );
2257 }
2258
2259 __PACKAGE__->register_method(
2260         method  => "retrieve_groups_tree",
2261         api_name        => "open-ils.actor.groups.tree.retrieve",
2262         notes           => <<"  NOTES");
2263         Returns a list of user groups
2264         NOTES
2265 sub retrieve_groups_tree {
2266         my( $self, $client ) = @_;
2267         return new_editor()->search_permission_grp_tree(
2268                 [
2269                         { parent => undef},
2270                         {       
2271                                 flesh                           => 10, 
2272                                 flesh_fields    => { pgt => ["children"] }, 
2273                                 order_by                        => { pgt => 'name'}
2274                         }
2275                 ]
2276         )->[0];
2277 }
2278
2279
2280 # turns an org list into an org tree
2281 =head old code
2282 sub build_group_tree {
2283
2284         my( $self, $grplist) = @_;
2285
2286         return $grplist unless ( 
2287                         ref($grplist) and @$grplist > 1 );
2288
2289         my @list = sort { $a->name cmp $b->name } @$grplist;
2290
2291         my $root;
2292         for my $grp (@list) {
2293
2294                 if ($grp and !defined($grp->parent)) {
2295                         $root = $grp;
2296                         next;
2297                 }
2298                 my ($parent) = grep { $_->id == $grp->parent} @list;
2299
2300                 $parent->children([]) unless defined($parent->children); 
2301                 push( @{$parent->children}, $grp );
2302         }
2303
2304         return $root;
2305 }
2306 =cut
2307
2308
2309 __PACKAGE__->register_method(
2310         method  => "add_user_to_groups",
2311         api_name        => "open-ils.actor.user.set_groups",
2312         notes           => <<"  NOTES");
2313         Adds a user to one or more permission groups
2314         NOTES
2315
2316 sub add_user_to_groups {
2317         my( $self, $client, $authtoken, $userid, $groups ) = @_;
2318
2319         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2320                 $authtoken, $userid, 'CREATE_USER_GROUP_LINK' );
2321         return $evt if $evt;
2322
2323         ( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2324                 $authtoken, $userid, 'REMOVE_USER_GROUP_LINK' );
2325         return $evt if $evt;
2326
2327         $apputils->simplereq(
2328                 'open-ils.storage',
2329                 'open-ils.storage.direct.permission.usr_grp_map.mass_delete', { usr => $userid } );
2330                 
2331         for my $group (@$groups) {
2332                 my $link = Fieldmapper::permission::usr_grp_map->new;
2333                 $link->grp($group);
2334                 $link->usr($userid);
2335
2336                 my $id = $apputils->simplereq(
2337                         'open-ils.storage',
2338                         'open-ils.storage.direct.permission.usr_grp_map.create', $link );
2339         }
2340
2341         return 1;
2342 }
2343
2344 __PACKAGE__->register_method(
2345         method  => "get_user_perm_groups",
2346         api_name        => "open-ils.actor.user.get_groups",
2347         notes           => <<"  NOTES");
2348         Retrieve a user's permission groups.
2349         NOTES
2350
2351
2352 sub get_user_perm_groups {
2353         my( $self, $client, $authtoken, $userid ) = @_;
2354
2355         my( $requestor, $target, $evt ) = $apputils->checkses_requestor(
2356                 $authtoken, $userid, 'VIEW_PERM_GROUPS' );
2357         return $evt if $evt;
2358
2359         return $apputils->simplereq(
2360                 'open-ils.cstore',
2361                 'open-ils.cstore.direct.permission.usr_grp_map.search.atomic', { usr => $userid } );
2362 }       
2363
2364 __PACKAGE__->register_method(
2365         method  => "get_user_work_ous",
2366         api_name        => "open-ils.actor.user.get_work_ous",
2367         notes           => <<"  NOTES");
2368         Retrieve a user's work org units.
2369         NOTES
2370
2371
2372 sub get_user_work_ous {
2373         my( $self, $client, $authtoken, $userid ) = @_;
2374
2375         my( $requestor, $evt ) = $apputils->checksesperm( $authtoken, 'ASSIGN_WORK_ORG_UNIT' );
2376         return $evt if $evt;
2377
2378         return $apputils->simplereq(
2379                 'open-ils.cstore',
2380                 'open-ils.cstore.direct.permission.usr_work_ou_map.search.atomic', { usr => $userid } );
2381 }       
2382
2383
2384
2385 __PACKAGE__->register_method (
2386         method          => 'register_workstation',
2387         api_name                => 'open-ils.actor.workstation.register.override',
2388         signature       => q/@see open-ils.actor.workstation.register/);
2389
2390 __PACKAGE__->register_method (
2391         method          => 'register_workstation',
2392         api_name                => 'open-ils.actor.workstation.register',
2393         signature       => q/
2394                 Registers a new workstion in the system
2395                 @param authtoken The login session key
2396                 @param name The name of the workstation id
2397                 @param owner The org unit that owns this workstation
2398                 @return The workstation id on success, WORKSTATION_NAME_EXISTS
2399                 if the name is already in use.
2400         /);
2401
2402 sub _register_workstation {
2403         my( $self, $connection, $authtoken, $name, $owner ) = @_;
2404         my( $requestor, $evt ) = $U->checkses($authtoken);
2405         return $evt if $evt;
2406         $evt = $U->check_perms($requestor->id, $owner, 'REGISTER_WORKSTATION');
2407         return $evt if $evt;
2408
2409         my $ws = $U->cstorereq(
2410                 'open-ils.cstore.direct.actor.workstation.search', { name => $name } );
2411         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS') if $ws;
2412
2413         $ws = Fieldmapper::actor::workstation->new;
2414         $ws->owning_lib($owner);
2415         $ws->name($name);
2416
2417         my $id = $U->storagereq(
2418                 'open-ils.storage.direct.actor.workstation.create', $ws );
2419         return $U->DB_UPDATE_FAILED($ws) unless $id;
2420
2421         $ws->id($id);
2422         return $ws->id();
2423 }
2424
2425 sub register_workstation {
2426         my( $self, $conn, $authtoken, $name, $owner ) = @_;
2427
2428         my $e = new_editor(authtoken=>$authtoken, xact=>1);
2429         return $e->event unless $e->checkauth;
2430         return $e->event unless $e->allowed('REGISTER_WORKSTATION'); # XXX rely on editor perms
2431         my $existing = $e->search_actor_workstation({name => $name});
2432
2433         if( @$existing ) {
2434                 if( $self->api_name =~ /override/o ) {
2435                         return $e->event unless $e->allowed('DELETE_WORKSTATION'); # XXX rely on editor perms
2436                         return $e->event unless $e->delete_actor_workstation($$existing[0]);
2437                 } else {
2438                         return OpenILS::Event->new('WORKSTATION_NAME_EXISTS')
2439                 }
2440         }
2441
2442         my $ws = Fieldmapper::actor::workstation->new;
2443         $ws->owning_lib($owner);
2444         $ws->name($name);
2445         $e->create_actor_workstation($ws) or return $e->event;
2446         $e->commit;
2447         return $ws->id; # note: editor sets the id on the new object for us
2448 }
2449
2450
2451 __PACKAGE__->register_method (
2452         method          => 'fetch_patron_note',
2453         api_name                => 'open-ils.actor.note.retrieve.all',
2454         signature       => q/
2455                 Returns a list of notes for a given user
2456                 Requestor must have VIEW_USER permission if pub==false and
2457                 @param authtoken The login session key
2458                 @param args Hash of params including
2459                         patronid : the patron's id
2460                         pub : true if retrieving only public notes
2461         /
2462 );
2463
2464 sub fetch_patron_note {
2465         my( $self, $conn, $authtoken, $args ) = @_;
2466         my $patronid = $$args{patronid};
2467
2468         my($reqr, $evt) = $U->checkses($authtoken);
2469         return $evt if $evt;
2470
2471         my $patron;
2472         ($patron, $evt) = $U->fetch_user($patronid);
2473         return $evt if $evt;
2474
2475         if($$args{pub}) {
2476                 if( $patronid ne $reqr->id ) {
2477                         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2478                         return $evt if $evt;
2479                 }
2480                 return $U->cstorereq(
2481                         'open-ils.cstore.direct.actor.usr_note.search.atomic', 
2482                         { usr => $patronid, pub => 't' } );
2483         }
2484
2485         $evt = $U->check_perms($reqr->id, $patron->home_ou, 'VIEW_USER');
2486         return $evt if $evt;
2487
2488         return $U->cstorereq(
2489                 'open-ils.cstore.direct.actor.usr_note.search.atomic', { usr => $patronid } );
2490 }
2491
2492 __PACKAGE__->register_method (
2493         method          => 'create_user_note',
2494         api_name                => 'open-ils.actor.note.create',
2495         signature       => q/
2496                 Creates a new note for the given user
2497                 @param authtoken The login session key
2498                 @param note The note object
2499         /
2500 );
2501 sub create_user_note {
2502         my( $self, $conn, $authtoken, $note ) = @_;
2503         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2504         return $e->die_event unless $e->checkauth;
2505
2506         my $user = $e->retrieve_actor_user($note->usr)
2507                 or return $e->die_event;
2508
2509         return $e->die_event unless 
2510                 $e->allowed('UPDATE_USER',$user->home_ou);
2511
2512         $note->creator($e->requestor->id);
2513         $e->create_actor_usr_note($note) or return $e->die_event;
2514         $e->commit;
2515         return $note->id;
2516 }
2517
2518
2519 __PACKAGE__->register_method (
2520         method          => 'delete_user_note',
2521         api_name                => 'open-ils.actor.note.delete',
2522         signature       => q/
2523                 Deletes a note for the given user
2524                 @param authtoken The login session key
2525                 @param noteid The note id
2526         /
2527 );
2528 sub delete_user_note {
2529         my( $self, $conn, $authtoken, $noteid ) = @_;
2530
2531         my $e = new_editor(xact=>1, authtoken=>$authtoken);
2532         return $e->die_event unless $e->checkauth;
2533         my $note = $e->retrieve_actor_usr_note($noteid)
2534                 or return $e->die_event;
2535         my $user = $e->retrieve_actor_user($note->usr)
2536                 or return $e->die_event;
2537         return $e->die_event unless 
2538                 $e->allowed('UPDATE_USER', $user->home_ou);
2539         
2540         $e->delete_actor_usr_note($note) or return $e->die_event;
2541         $e->commit;
2542         return 1;
2543 }
2544
2545
2546 __PACKAGE__->register_method (
2547         method          => 'update_user_note',
2548         api_name                => 'open-ils.actor.note.update',
2549         signature       => q/
2550                 @param authtoken The login session key
2551                 @param note The note
2552         /
2553 );
2554
2555 sub update_user_note {
2556         my( $self, $conn, $auth, $note ) = @_;
2557         my $e = new_editor(authtoken=>$auth, xact=>1);
2558         return $e->event unless $e->checkauth;
2559         my $patron = $e->retrieve_actor_user($note->usr)
2560                 or return $e->event;
2561         return $e->event unless 
2562                 $e->allowed('UPDATE_USER', $patron->home_ou);
2563         $e->update_actor_user_note($note)
2564                 or return $e->event;
2565         $e->commit;
2566         return 1;
2567 }
2568
2569
2570
2571
2572 __PACKAGE__->register_method (
2573         method          => 'create_closed_date',
2574         api_name        => 'open-ils.actor.org_unit.closed_date.create',
2575         signature       => q/
2576                 Creates a new closing entry for the given org_unit
2577                 @param authtoken The login session key
2578                 @param note The closed_date object
2579         /
2580 );
2581 sub create_closed_date {
2582         my( $self, $conn, $authtoken, $cd ) = @_;
2583
2584         my( $user, $evt ) = $U->checkses($authtoken);
2585         return $evt if $evt;
2586
2587         $evt = $U->check_perms($user->id, $cd->org_unit, 'CREATE_CLOSEING');
2588         return $evt if $evt;
2589
2590         $logger->activity("user ".$user->id." creating library closing for ".$cd->org_unit);
2591
2592         my $id = $U->storagereq(
2593                 'open-ils.storage.direct.actor.org_unit.closed_date.create', $cd );
2594         return $U->DB_UPDATE_FAILED($cd) unless $id;
2595         return $id;
2596 }
2597
2598
2599 __PACKAGE__->register_method (
2600         method          => 'delete_closed_date',
2601         api_name        => 'open-ils.actor.org_unit.closed_date.delete',
2602         signature       => q/
2603                 Deletes a closing entry for the given org_unit
2604                 @param authtoken The login session key
2605                 @param noteid The close_date id
2606         /
2607 );
2608 sub delete_closed_date {
2609         my( $self, $conn, $authtoken, $cd ) = @_;
2610
2611         my( $user, $evt ) = $U->checkses($authtoken);
2612         return $evt if $evt;
2613
2614         my $cd_obj;
2615         ($cd_obj, $evt) = fetch_closed_date($cd);
2616         return $evt if $evt;
2617
2618         $evt = $U->check_perms($user->id, $cd->org_unit, 'DELETE_CLOSEING');
2619         return $evt if $evt;
2620
2621         $logger->activity("user ".$user->id." deleting library closing for ".$cd->org_unit);
2622
2623         my $stat = $U->storagereq(
2624                 'open-ils.storage.direct.actor.org_unit.closed_date.delete', $cd );
2625         return $U->DB_UPDATE_FAILED($cd) unless $stat;
2626         return $stat;
2627 }
2628
2629
2630 __PACKAGE__->register_method(
2631         method => 'usrname_exists',
2632         api_name        => 'open-ils.actor.username.exists',
2633         signature => q/
2634                 Returns 1 if the requested username exists, returns 0 otherwise
2635         /
2636 );
2637
2638 sub usrname_exists {
2639         my( $self, $conn, $auth, $usrname ) = @_;
2640         my $e = new_editor(authtoken=>$auth);
2641         return $e->event unless $e->checkauth;
2642         my $a = $e->search_actor_user({usrname => $usrname, deleted=>'f'}, {idlist=>1});
2643         return $$a[0] if $a and @$a;
2644         return 0;
2645 }
2646
2647 __PACKAGE__->register_method(
2648         method => 'barcode_exists',
2649         api_name        => 'open-ils.actor.barcode.exists',
2650         signature => q/
2651                 Returns 1 if the requested barcode exists, returns 0 otherwise
2652         /
2653 );
2654
2655 sub barcode_exists {
2656         my( $self, $conn, $auth, $barcode ) = @_;
2657         my $e = new_editor(authtoken=>$auth);
2658         return $e->event unless $e->checkauth;
2659         my $a = $e->search_actor_card({barcode => $barcode}, {idlist=>1});
2660         return $$a[0] if $a and @$a;
2661         return 0;
2662 }
2663
2664
2665 __PACKAGE__->register_method(
2666         method => 'retrieve_net_levels',
2667         api_name        => 'open-ils.actor.net_access_level.retrieve.all',
2668 );
2669
2670 sub retrieve_net_levels {
2671         my( $self, $conn, $auth ) = @_;
2672         my $e = new_editor(authtoken=>$auth);
2673         return $e->event unless $e->checkauth;
2674         return $e->retrieve_all_config_net_access_level();
2675 }
2676
2677
2678 __PACKAGE__->register_method(
2679         method => 'fetch_org_by_shortname',
2680         api_name => 'open-ils.actor.org_unit.retrieve_by_shorname',
2681 );
2682 sub fetch_org_by_shortname {
2683         my( $self, $conn, $sname ) = @_;
2684         my $e = new_editor();
2685         my $org = $e->search_actor_org_unit({ shortname => uc($sname)})->[0];
2686         return $e->event unless $org;
2687         return $org;
2688 }
2689
2690
2691 __PACKAGE__->register_method(
2692         method => 'session_home_lib',
2693         api_name => 'open-ils.actor.session.home_lib',
2694 );
2695
2696 sub session_home_lib {
2697         my( $self, $conn, $auth ) = @_;
2698         my $e = new_editor(authtoken=>$auth);
2699         return undef unless $e->checkauth;
2700         my $org = $e->retrieve_actor_org_unit($e->requestor->home_ou);
2701         return $org->shortname;
2702 }
2703
2704 __PACKAGE__->register_method(
2705         method => 'session_safe_token',
2706         api_name => 'open-ils.actor.session.safe_token',
2707         signature => q/
2708                 Returns a hashed session ID that is safe for export to the world.
2709                 This safe token will expire after 1 hour of non-use.
2710                 @param auth Active authentication token
2711         /
2712 );
2713
2714 sub session_safe_token {
2715         my( $self, $conn, $auth ) = @_;
2716         my $e = new_editor(authtoken=>$auth);
2717         return undef unless $e->checkauth;
2718
2719         my $safe_token = md5_hex($auth);
2720
2721         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2722
2723         # Add more like the following if needed...
2724         $cache->put_cache(
2725                 "safe-token-home_lib-shortname-$safe_token",
2726                 $e->retrieve_actor_org_unit(
2727                         $e->requestor->home_ou
2728                 )->shortname,
2729                 60 * 60
2730         );
2731
2732         return $safe_token;
2733 }
2734
2735
2736 __PACKAGE__->register_method(
2737         method => 'safe_token_home_lib',
2738         api_name => 'open-ils.actor.safe_token.home_lib.shortname',
2739         signature => q/
2740                 Returns the home library shortname from the session
2741                 asscociated with a safe token from generated by
2742                 open-ils.actor.session.safe_token.
2743                 @param safe_token Active safe token
2744         /
2745 );
2746
2747 sub safe_token_home_lib {
2748         my( $self, $conn, $safe_token ) = @_;
2749
2750         $cache ||= OpenSRF::Utils::Cache->new("global", 0);
2751         return $cache->get_cache( 'safe-token-home_lib-shortname-'. $safe_token );
2752 }
2753
2754
2755
2756 __PACKAGE__->register_method(
2757         method => 'slim_tree',
2758         api_name        => "open-ils.actor.org_tree.slim_hash.retrieve",
2759 );
2760 sub slim_tree {
2761         my $tree = new_editor()->search_actor_org_unit( 
2762                 [
2763                         {"parent_ou" => undef },
2764                         {
2765                                 flesh                           => 2,
2766                                 flesh_fields    => { aou =>  ['children'] },
2767                                 order_by                        => { aou => 'name'},
2768                                 select                  => { aou => ["id","shortname", "name"]},
2769                         }
2770                 ]
2771         )->[0];
2772
2773         return trim_tree($tree);
2774 }
2775
2776
2777 sub trim_tree {
2778         my $tree = shift;
2779         return undef unless $tree;
2780         my $htree = {
2781                 code => $tree->shortname,
2782                 name => $tree->name,
2783         };
2784         if( $tree->children and @{$tree->children} ) {
2785                 $htree->{children} = [];
2786                 for my $c (@{$tree->children}) {
2787                         push( @{$htree->{children}}, trim_tree($c) );
2788                 }
2789         }
2790
2791         return $htree;
2792 }
2793
2794
2795 __PACKAGE__->register_method(
2796         method  => "update_penalties",
2797         api_name        => "open-ils.actor.user.penalties.update");
2798 sub update_penalties {
2799         my( $self, $conn, $auth, $userid ) = @_;
2800         my $e = new_editor(authtoken=>$auth);
2801         return $e->event unless $e->checkauth;
2802         $U->update_patron_penalties( 
2803                 authtoken => $auth,
2804                 patronid  => $userid,
2805         );
2806         return 1;
2807 }
2808
2809
2810
2811 __PACKAGE__->register_method(
2812         method  => "user_retrieve_fleshed_by_id",
2813         api_name        => "open-ils.actor.user.fleshed.retrieve",);
2814
2815 sub user_retrieve_fleshed_by_id {
2816         my( $self, $client, $auth, $user_id, $fields ) = @_;
2817         my $e = new_editor(authtoken => $auth);
2818         return $e->event unless $e->checkauth;
2819
2820         if( $e->requestor->id != $user_id ) {
2821                 return $e->event unless $e->allowed('VIEW_USER');
2822         }
2823
2824         $fields ||= [
2825                 "cards",
2826                 "card",
2827                 "standing_penalties",
2828                 "addresses",
2829                 "billing_address",
2830                 "mailing_address",
2831                 "stat_cat_entries" ];
2832         return new_flesh_user($user_id, $fields, $e);
2833 }
2834
2835
2836 sub new_flesh_user {
2837
2838         my $id = shift;
2839         my $fields = shift || [];
2840         my $e   = shift || new_editor(xact=>1);
2841
2842         my $user = $e->retrieve_actor_user(
2843         [
2844         $id,
2845         {
2846                 "flesh"                         => 1,
2847                 "flesh_fields" =>  { "au" => $fields }
2848         }
2849         ]
2850         ) or return $e->event;
2851
2852
2853         if( grep { $_ eq 'addresses' } @$fields ) {
2854
2855                 $user->addresses([]) unless @{$user->addresses};
2856         
2857                 if( ref $user->billing_address ) {
2858                         unless( grep { $user->billing_address->id == $_->id } @{$user->addresses} ) {
2859                                 push( @{$user->addresses}, $user->billing_address );
2860                         }
2861                 }
2862         
2863                 if( ref $user->mailing_address ) {
2864                         unless( grep { $user->mailing_address->id == $_->id } @{$user->addresses} ) {
2865                                 push( @{$user->addresses}, $user->mailing_address );
2866                         }
2867                 }
2868         }
2869
2870         $e->rollback;
2871         $user->clear_passwd();
2872         return $user;
2873 }
2874
2875
2876
2877
2878 __PACKAGE__->register_method(
2879         method  => "user_retrieve_parts",
2880         api_name        => "open-ils.actor.user.retrieve.parts",);
2881
2882 sub user_retrieve_parts {
2883         my( $self, $client, $auth, $user_id, $fields ) = @_;
2884         my $e = new_editor(authtoken => $auth);
2885         return $e->event unless $e->checkauth;
2886         if( $e->requestor->id != $user_id ) {
2887                 return $e->event unless $e->allowed('VIEW_USER');
2888         }
2889         my @resp;
2890         my $user = $e->retrieve_actor_user($user_id) or return $e->event;
2891         push(@resp, $user->$_()) for(@$fields);
2892         return \@resp;
2893 }
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903 1;
2904