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