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