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