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