8cd9f24133b7525f5d2597539993c779e7d06ad3
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Storage / Publisher / actor.pm
1 package OpenILS::Application::Storage::Publisher::actor;
2 use base qw/OpenILS::Application::Storage/;
3 use OpenILS::Application::Storage::CDBI::actor;
4 use OpenSRF::Utils::Logger qw/:level/;
5 use OpenSRF::Utils qw/:datetime/;
6 use OpenILS::Utils::Fieldmapper;
7 use OpenSRF::Utils::SettingsClient;
8
9 use DateTime;           
10 use DateTime::Format::ISO8601;  
11                                                 
12                                                                                                 
13 my $_dt_parser = DateTime::Format::ISO8601->new;    
14
15 my $log = 'OpenSRF::Utils::Logger';
16
17 sub new_usergroup_id {
18         return actor::user->db_Main->selectrow_array("select nextval('actor.usr_usrgroup_seq'::regclass)");
19 }
20 __PACKAGE__->register_method(
21         api_name        => 'open-ils.storage.actor.user.group_id.new',
22         api_level       => 1,
23         method          => 'new_usergroup_id',
24 );
25
26 sub usr_total_owed {
27         my $self = shift;
28         my $client = shift;
29         my $usr = shift;
30
31         my $sql = <<"   SQL";
32                         SELECT  x.usr,
33                                         SUM(COALESCE((SELECT SUM(b.amount) FROM money.billing b WHERE b.voided IS FALSE AND b.xact = x.id),0.0)) -
34                                                 SUM(COALESCE((SELECT SUM(p.amount) FROM money.payment p WHERE p.voided IS FALSE AND p.xact = x.id),0.0))
35                           FROM  money.billable_xact x
36                           WHERE x.usr = ? AND x.xact_finish IS NULL
37                           GROUP BY 1
38         SQL
39
40         my (undef,$val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
41
42         return $val;
43 }
44 __PACKAGE__->register_method(
45         api_name        => 'open-ils.storage.actor.user.total_owed',
46         api_level       => 1,
47         method          => 'usr_total_owed',
48 );
49
50 sub usr_breakdown_out {
51         my $self = shift;
52         my $client = shift;
53         my $usr = shift;
54
55         my $out_sql = <<"       SQL";
56                         SELECT  id
57                           FROM  action.circulation
58                           WHERE usr = ? AND checkin_time IS NULL AND due_date >= 'today' AND (stop_fines IS NULL OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
59                           WHERE usr = ?
60                     AND checkin_time IS NULL
61                     AND (  (fine_interval >= '1 day' AND due_date >= 'today')
62                         OR (fine_interval < '1 day'  AND due_date > 'now'   ))
63                     AND (stop_fines IS NULL
64                         OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
65         SQL
66
67         my $out = actor::user->db_Main->selectcol_arrayref($out_sql, {}, $usr);
68
69         my $od_sql = <<"        SQL";
70                         SELECT  id
71                           FROM  action.circulation
72                           WHERE usr = ?
73                     AND checkin_time IS NULL
74                     AND (  (fine_interval >= '1 day' AND due_date < 'today')
75                         OR (fine_interval < '1 day'  AND due_date < 'now'  ))
76                     AND (stop_fines IS NULL
77                         OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
78         SQL
79
80         my $od = actor::user->db_Main->selectcol_arrayref($od_sql, {}, $usr);
81
82         my $lost_sql = <<"      SQL";
83                         SELECT  id
84                           FROM  action.circulation
85                           WHERE usr = ? AND checkin_time IS NULL AND xact_finish IS NULL AND stop_fines = 'LOST'
86         SQL
87
88         my $lost = actor::user->db_Main->selectcol_arrayref($lost_sql, {}, $usr);
89
90         my $cl_sql = <<"        SQL";
91                         SELECT  id
92                           FROM  action.circulation
93                           WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'CLAIMSRETURNED'
94         SQL
95
96         my $cl = actor::user->db_Main->selectcol_arrayref($cl_sql, {}, $usr);
97
98         my $lo_sql = <<"        SQL";
99                         SELECT  id
100                           FROM  action.circulation
101                           WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'LONGOVERDUE'
102         SQL
103
104         my $lo = actor::user->db_Main->selectcol_arrayref($lo_sql, {}, $usr);
105
106         if ($self->api_name =~/count$/o) {
107                 return {        total   => scalar(@$out) + scalar(@$od) + scalar(@$lost) + scalar(@$cl) + scalar(@$lo),
108                                         out             => scalar(@$out),
109                                         overdue => scalar(@$od),
110                                         lost    => scalar(@$lost),
111                                         claims_returned => scalar(@$cl),
112                                         long_overdue            => scalar(@$lo),
113                 };
114         }
115
116         return {        out             => $out,
117                                 overdue => $od,
118                                 lost    => $lost,
119                                 claims_returned => $cl,
120                                 long_overdue            => $lo,
121         };
122 }
123 __PACKAGE__->register_method(
124         api_name        => 'open-ils.storage.actor.user.checked_out',
125         api_level       => 1,
126         method          => 'usr_breakdown_out',
127 );
128 __PACKAGE__->register_method(
129         api_name        => 'open-ils.storage.actor.user.checked_out.count',
130         api_level       => 1,
131         method          => 'usr_breakdown_out',
132 );
133
134 sub usr_total_out {
135         my $self = shift;
136         my $client = shift;
137         my $usr = shift;
138
139         my $sql = <<"   SQL";
140                         SELECT  count(*)
141                           FROM  action.circulation
142                           WHERE usr = ? AND checkin_time IS NULL
143         SQL
144
145         my ($val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
146
147         return $val;
148 }
149 __PACKAGE__->register_method(
150         api_name        => 'open-ils.storage.actor.user.total_out',
151         api_level       => 1,
152         method          => 'usr_total_out',
153 );
154
155 sub calc_proximity {
156         my $self = shift;
157         my $client = shift;
158
159         local $OpenILS::Application::Storage::WRITE = 1;
160
161         my $delete_sql = <<"    SQL";
162                 DELETE FROM actor.org_unit_proximity;
163         SQL
164
165         my $insert_sql = <<"    SQL";
166                 INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
167                         SELECT  l.id,
168                                 r.id,
169                                 actor.org_unit_proximity(l.id,r.id)
170                           FROM  actor.org_unit l,
171                                 actor.org_unit r;
172         SQL
173
174         actor::org_unit_proximity->db_Main->do($delete_sql);
175         actor::org_unit_proximity->db_Main->do($insert_sql);
176
177         return 1;
178 }
179 __PACKAGE__->register_method(
180         api_name        => 'open-ils.storage.actor.org_unit.refresh_proximity',
181         api_level       => 1,
182         method          => 'calc_proximity',
183 );
184
185
186 sub org_closed_overlap {
187         my $self = shift;
188         my $client = shift;
189         my $ou = shift;
190         my $date = shift;
191         my $direction = shift || 0;
192         my $no_hoo = shift || 0;
193
194         return undef unless ($date && $ou);
195
196         my $t = actor::org_unit::closed_date->table;
197         my $sql = <<"   SQL";
198                 SELECT  *
199                   FROM  $t
200                   WHERE ? between close_start and close_end
201                         AND org_unit = ?
202                   ORDER BY close_start ASC, close_end DESC
203                   LIMIT 1
204         SQL
205
206         $date = clense_ISO8601($date);
207         my ($begin, $end) = ($date,$date);
208
209         my $hoo = actor::org_unit::hours_of_operation->retrieve($ou);
210
211         if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $date, $ou )) {
212                 $begin = clense_ISO8601($closure->{close_start});
213                 $end = clense_ISO8601($closure->{close_end});
214
215                 if ( $direction <= 0 ) {
216                         $before = $_dt_parser->parse_datetime( $begin );
217                         $before->subtract( minutes => 1 );
218
219                         while ( my $_b = org_closed_overlap($self, $client, $ou, $before->iso8601, -1, 1 ) ) {
220                                 $before = $_dt_parser->parse_datetime( clense_ISO8601($_b->{start}) );
221                         }
222                         $begin = clense_ISO8601($before->iso8601);
223                 }
224
225                 if ( $direction >= 0 ) {
226                         $after = $_dt_parser->parse_datetime( $end );
227                         $after->add( minutes => 1 );
228
229                         while ( my $_a = org_closed_overlap($self, $client, $ou, $after->iso8601, 1, 1 ) ) {
230                                 $after = $_dt_parser->parse_datetime( clense_ISO8601($_a->{end}) );
231                         }
232                         $end = clense_ISO8601($after->iso8601);
233                 }
234         }
235
236         if ( !$no_hoo ) {
237                 if ( $hoo ) {
238
239                         if ( $direction <= 0 ) {
240                                 my $begin_dow = $_dt_parser->parse_datetime( $begin )->day_of_week_0;
241                                 my $begin_open_meth = "dow_".$begin_dow."_open";
242                                 my $begin_close_meth = "dow_".$begin_dow."_close";
243
244                                 my $count = 1;
245                                 while ($hoo->$begin_open_meth eq '00:00:00' and $hoo->$begin_close_meth eq '00:00:00') {
246                                         $begin = clense_ISO8601($_dt_parser->parse_datetime( $begin )->subtract( days => 1)->iso8601);
247                                         $begin_dow++;
248                                         $begin_dow %= 7;
249                                         $count++;
250                                         last if ($count > 6);
251                                         $begin_open_meth = "dow_".$begin_dow."_open";
252                                         $begin_close_meth = "dow_".$begin_dow."_close";
253                                 }
254
255                                 if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $begin, $ou )) {
256                                         $before = $_dt_parser->parse_datetime( $begin );
257                                         $before->subtract( minutes => 1 );
258                                         while ( my $_b = org_closed_overlap($self, $client, $ou, $before->iso8601, -1 ) ) {
259                                                 $before = $_dt_parser->parse_datetime( clense_ISO8601($_b->{start}) );
260                                         }
261                                 }
262                         }
263         
264                         if ( $direction >= 0 ) {
265                                 my $end_dow = $_dt_parser->parse_datetime( $end )->day_of_week_0;
266                                 my $end_open_meth = "dow_".$end_dow."_open";
267                                 my $end_close_meth = "dow_".$end_dow."_close";
268         
269                                 $count = 1;
270                                 while ($hoo->$end_open_meth eq '00:00:00' and $hoo->$end_close_meth eq '00:00:00') {
271                                         $end = clense_ISO8601($_dt_parser->parse_datetime( $end )->add( days => 1)->iso8601);
272                                         $end_dow++;
273                                         $end_dow %= 7;
274                                         $count++;
275                                         last if ($count > 6);
276                                         $end_open_meth = "dow_".$end_dow."_open";
277                                         $end_close_meth = "dow_".$end_dow."_close";
278                                 }
279
280                                 if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $end, $ou )) {
281                                         $after = $_dt_parser->parse_datetime( $end );
282                                         $after->add( minutes => 1 );
283
284                                         while ( my $_a = org_closed_overlap($self, $client, $ou, $after->iso8601, 1 ) ) {
285                                                 $after = $_dt_parser->parse_datetime( clense_ISO8601($_a->{end}) );
286                                         }
287                                         $end = clense_ISO8601($after->iso8601);
288                                 }
289                         }
290
291                 }
292         }
293
294         if ($begin eq $date && $end eq $date) {
295                 return undef;
296         }
297
298         return { start => $begin, end => $end };
299 }
300 __PACKAGE__->register_method(
301         api_name        => 'open-ils.storage.actor.org_unit.closed_date.overlap',
302         api_level       => 1,
303         method          => 'org_closed_overlap',
304 );
305
306 sub user_by_barcode {
307         my $self = shift;
308         my $client = shift;
309         my @barcodes = shift;
310
311         return undef unless @barcodes;
312
313         for my $card ( actor::card->search( { barcode => @barcodes } ) ) {
314                 next unless $card;
315                 if (@barcodes == 1) {
316                         return $card->usr->to_fieldmapper;
317                 }
318                 $client->respond( $card->usr->to_fieldmapper);
319         }
320         return undef;
321 }
322 __PACKAGE__->register_method(
323         api_name        => 'open-ils.storage.direct.actor.user.search.barcode',
324         api_level       => 1,
325         method          => 'user_by_barcode',
326         stream          => 1,
327         cachable        => 1,
328 );
329
330 sub lost_barcodes {
331         my $self = shift;
332         my $client = shift;
333
334         my $c = actor::card->table;
335         my $p = actor::user->table;
336
337         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.card <> c.id";
338
339         my $list = actor::user->db_Main->selectcol_arrayref($sql);
340         for my $bc ( @$list ) {
341                 $client->respond($bc);
342         }
343         return undef;
344 }
345 __PACKAGE__->register_method(
346         api_name        => 'open-ils.storage.actor.user.lost_barcodes',
347         api_level       => 1,
348         stream          => 1,
349         method          => 'lost_barcodes',
350         signature       => <<'  NOTE',
351                 Returns an array of barcodes that belong to lost cards.
352                 @return array of barcodes
353         NOTE
354 );
355
356 sub expired_barcodes {
357         my $self = shift;
358         my $client = shift;
359
360         my $c = actor::card->table;
361         my $p = actor::user->table;
362
363         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.expire_date < CURRENT_DATE";
364
365         my $list = actor::user->db_Main->selectcol_arrayref($sql);
366         for my $bc ( @$list ) {
367                 $client->respond($bc);
368         }
369         return undef;
370 }
371 __PACKAGE__->register_method(
372         api_name        => 'open-ils.storage.actor.user.expired_barcodes',
373         api_level       => 1,
374         stream          => 1,
375         method          => 'expired_barcodes',
376         signature       => <<'  NOTE',
377                 Returns an array of barcodes that are currently expired.
378                 @return array of barcodes
379         NOTE
380 );
381
382 sub barred_barcodes {
383         my $self = shift;
384         my $client = shift;
385
386         my $c = actor::card->table;
387         my $p = actor::user->table;
388
389         my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.barred IS TRUE";
390
391         my $list = actor::user->db_Main->selectcol_arrayref($sql);
392         for my $bc ( @$list ) {
393                 $client->respond($bc);
394         }
395         return undef;
396 }
397 __PACKAGE__->register_method(
398         api_name        => 'open-ils.storage.actor.user.barred_barcodes',
399         api_level       => 1,
400         stream          => 1,
401         method          => 'barred_barcodes',
402         signature       => <<'  NOTE',
403                 Returns an array of barcodes that are currently barred.
404                 @return array of barcodes
405         NOTE
406 );
407
408 sub penalized_barcodes {
409         my $self = shift;
410         my $client = shift;
411         my @ignore = @_;
412
413         my $c = actor::card->table;
414         my $p = actor::user_standing_penalty->table;
415
416         my $sql = "SELECT c.barcode FROM $c c JOIN $p p USING (usr)";
417
418         if (@ignore) {
419                 $sql .= ' WHERE penalty_type NOT IN ('. join(',', map { '?' } @ignore) . ')';
420         }
421
422         $sql .= ' GROUP BY c.barcode;';
423
424         my $list = actor::user->db_Main->selectcol_arrayref($sql, {}, @ignore);
425         for my $bc ( @$list ) {
426                 $client->respond($bc);
427         }
428         return undef;
429 }
430 __PACKAGE__->register_method(
431         api_name        => 'open-ils.storage.actor.user.penalized_barcodes',
432         api_level       => 1,
433         stream          => 1,
434         method          => 'penalized_barcodes',
435         signature       => <<'  NOTE',
436                 Returns an array of barcodes that have penalties not listed
437                 as a parameter.  Supply a list of any penalty types that should
438                 not stop a patron from checking out materials.
439
440                 @param ignore_list Penalty type to ignore
441                 @return array of barcodes
442         NOTE
443 );
444
445
446 sub patron_search {
447         my $self = shift;
448         my $client = shift;
449         my $search = shift;
450         my $limit = shift || 1000;
451         my $sort = shift;
452         my $inactive = shift;
453         my $ws_ou = shift;
454         my $ws_ou_depth = shift || 0;
455
456         my $strict_opt_in = OpenSRF::Utils::SettingsClient->new->config_value( share => user => 'opt_in' );
457
458         $sort = ['family_name','first_given_name'] unless ($$sort[0]);
459         push @$sort,'id';
460
461         # group 0 = user
462         # group 1 = address
463         # group 2 = phone, ident
464
465         my $usr = join ' AND ', map { "LOWER($_) ~ ?" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
466         my @usrv = map { "^$$search{$_}{value}" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
467
468         my $addr = join ' AND ', map { "LOWER($_) ~ ?" } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
469         my @addrv = map { "^$$search{$_}{value}" } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
470
471         my $pv = $$search{phone}{value};
472         my $iv = $$search{ident}{value};
473         my $nv = $$search{name}{value};
474
475         my $phone = '';
476         my @ps;
477         my @phonev;
478         if ($pv) {
479                 for my $p ( qw/day_phone evening_phone other_phone/ ) {
480                         push @ps, "LOWER($p) ~ ?";
481                         push @phonev, "^$pv";
482                 }
483                 $phone = '(' . join(' OR ', @ps) . ')';
484         }
485
486         my $ident = '';
487         my @is;
488         my @identv;
489         if ($iv) {
490                 for my $i ( qw/ident_value ident_value2/ ) {
491                         push @is, "LOWER($i) ~ ?";
492                         push @identv, "^$iv";
493                 }
494                 $ident = '(' . join(' OR ', @is) . ')';
495         }
496
497         my $name = '';
498         my @ns;
499         my @namev;
500         if (0 && $nv) {
501                 for my $n ( qw/first_given_name second_given_name family_name/ ) {
502                         push @ns, "LOWER($i) ~ ?";
503                         push @namev, "^$nv";
504                 }
505                 $name = '(' . join(' OR ', @ns) . ')';
506         }
507
508         my $usr_where = join ' AND ', grep { $_ } ($usr,$phone,$ident,$name);
509         my $addr_where = $addr;
510
511
512         my $u_table = actor::user->table;
513         my $a_table = actor::user_address->table;
514         my $opt_in_table = actor::usr_org_unit_opt_in->table;
515         my $ou_table = actor::org_unit->table;
516
517         my $u_select = "SELECT id as id FROM $u_table u WHERE $usr_where";
518         my $a_select = "SELECT usr as id FROM $a_table a WHERE $addr_where";
519         my $clone_select = '';
520         $clone_select = "JOIN (SELECT cu.id as id FROM $a_table ca ".
521                            "JOIN $u_table cu ON (cu.mailing_address = ca.id OR cu.billing_address = ca.id) ".
522                            "WHERE $addr_where) AS clone ON (clone.id = users.id)" if ($addr_where);
523
524         my $select = '';
525         if ($usr_where) {
526                 if ($addr_where) {
527                         $select = "$u_select INTERSECT $a_select";
528                 } else {
529                         $select = $u_select;
530                 }
531         } elsif ($addr_where) {
532                 $select = "$a_select";
533         } else {
534                 return undef;
535         }
536
537         my $order_by = join ', ', map { 'LOWER(users.'. (split / /,$_)[0] . ') ' . (split / /,$_)[1] } @$sort;
538         my $distinct_list = join ', ', map { 'LOWER(users.'. (split / /,$_)[0] . ')' } @$sort;
539
540         if ($inactive) {
541                 $inactive = '';
542         } else {
543                 $inactive = 'AND users.active = TRUE';
544         }
545
546         if (!$ws_ou) {  # XXX This should be required!!
547                 $ws_ou = actor::org_unit->search( { parent_ou => undef } )->next->id;
548         }
549
550         my $opt_in_join = '';
551         my $opt_in_where = '';
552         if (lc($strict_opt_in) eq 'true') {
553                 $opt_in_join = "LEFT JOIN $opt_in_table oi ON (oi.org_unit = $ws_ou AND users.id = oi.usr)";
554                 $opt_in_where = "AND (oi.id IS NOT NULL OR users.home_ou = $ws_ou)";
555         }
556
557         my $descendants = "actor.org_unit_descendants($ws_ou, $ws_ou_depth)";
558
559         $select = <<"   SQL";
560                 SELECT  DISTINCT $distinct_list
561                   FROM  $u_table AS users
562                         JOIN ($select) AS search ON (search.id = users.id)
563                         JOIN $descendants d ON (d.id = users.home_ou)
564                         $opt_in_join
565                         $clone_select
566                   WHERE users.deleted = FALSE
567                         $inactive
568                         $opt_in_where
569                   ORDER BY $order_by
570                   LIMIT $limit
571         SQL
572
573         return actor::user->db_Main->selectcol_arrayref($select, {Columns=>[scalar(@$sort)]}, map {lc($_)} (@usrv,@phonev,@identv,@namev,@addrv,@addrv));
574 }
575 __PACKAGE__->register_method(
576         api_name        => 'open-ils.storage.actor.user.crazy_search',
577         api_level       => 1,
578         method          => 'patron_search',
579 );
580
581 =comment not gonna use it...
582
583 sub fleshed_search {
584         my $self = shift;
585         my $client = shift;
586         my $searches = shift;
587
588         return undef unless (defined $searches);
589
590         for my $usr ( actor::user->search( $searches ) ) {
591                 next unless $usr;
592                 $client->respond( flesh_user( $usr ) );
593         }
594         return undef;
595 }
596 __PACKAGE__->register_method(
597         api_name        => 'open-ils.storage.fleshed.actor.user.search',
598         api_level       => 1,
599         method          => 'fleshed_search',
600         stream          => 1,
601         cachable        => 1,
602 );
603
604 sub fleshed_search_like {
605         my $self = shift;
606         my $client = shift;
607         my $searches = shift;
608
609         return undef unless (defined $searches);
610
611         for my $usr ( actor::user->search_like( $searches ) ) {
612                 next unless $usr;
613                 $client->respond( flesh_user( $usr ) );
614         }
615         return undef;
616 }
617 __PACKAGE__->register_method(
618         api_name        => 'open-ils.storage.fleshed.actor.user.search_like',
619         api_level       => 1,
620         method          => 'user_by_barcode',
621         stream          => 1,
622         cachable        => 1,
623 );
624
625 sub retrieve_fleshed_user {
626         my $self = shift;
627         my $client = shift;
628         my @ids = shift;
629
630         return undef unless @ids;
631
632         @ids = ($ids[0]) unless ($self->api_name =~ /batch/o); 
633
634         $client->respond( flesh_user( actor::user->retrieve( $_ ) ) ) for ( @ids );
635
636         return undef;
637 }
638 __PACKAGE__->register_method(
639         api_name        => 'open-ils.storage.fleshed.actor.user.retrieve',
640         api_level       => 1,
641         method          => 'retrieve_fleshed_user',
642         cachable        => 1,
643 );
644 __PACKAGE__->register_method(
645         api_name        => 'open-ils.storage.fleshed.actor.user.batch.retrieve',
646         api_level       => 1,
647         method          => 'retrieve_fleshed_user',
648         stream          => 1,
649         cachable        => 1,
650 );
651
652 sub flesh_user {
653         my $usr = shift;
654
655
656         my $standing = $usr->standing;
657         my $profile = $usr->profile;
658         my $ident_type = $usr->ident_type;
659                 
660         my $maddress = $usr->mailing_address;
661         my $baddress = $usr->billing_address;
662         my $card = $usr->card;
663
664         my @addresses = $usr->addresses;
665         my @cards = $usr->cards;
666
667         my $usr_fm = $usr->to_fieldmapper;
668         $usr_fm->standing( $standing->to_fieldmapper );
669         $usr_fm->profile( $profile->to_fieldmapper );
670         $usr_fm->ident_type( $ident_type->to_fieldmapper );
671
672         $usr_fm->card( $card->to_fieldmapper );
673         $usr_fm->mailing_address( $maddress->to_fieldmapper ) if ($maddress);
674         $usr_fm->billing_address( $baddress->to_fieldmapper ) if ($baddress);
675
676         $usr_fm->cards( [ map { $_->to_fieldmapper } @cards ] );
677         $usr_fm->addresses( [ map { $_->to_fieldmapper } @addresses ] );
678
679         return $usr_fm;
680 }
681
682 =cut
683
684 sub org_unit_list {
685         my $self = shift;
686         my $client = shift;
687
688         my $select =<<" SQL";
689         SELECT  *
690           FROM  actor.org_unit
691           ORDER BY CASE WHEN parent_ou IS NULL THEN 0 ELSE 1 END, name;
692         SQL
693
694         my $sth = actor::org_unit->db_Main->prepare_cached($select);
695         $sth->execute;
696
697         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
698
699         return undef;
700 }
701 __PACKAGE__->register_method(
702         api_name        => 'open-ils.storage.direct.actor.org_unit.retrieve.all',
703         api_level       => 1,
704         stream          => 1,
705         method          => 'org_unit_list',
706 );
707
708 sub org_unit_type_list {
709         my $self = shift;
710         my $client = shift;
711
712         my $select =<<" SQL";
713         SELECT  *
714           FROM  actor.org_unit_type
715           ORDER BY depth, name;
716         SQL
717
718         my $sth = actor::org_unit_type->db_Main->prepare_cached($select);
719         $sth->execute;
720
721         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit_type->construct($_) } $sth->fetchall_hash );
722
723         return undef;
724 }
725 __PACKAGE__->register_method(
726         api_name        => 'open-ils.storage.direct.actor.org_unit_type.retrieve.all',
727         api_level       => 1,
728         stream          => 1,
729         method          => 'org_unit_type_list',
730 );
731
732 sub org_unit_full_path {
733         my $self = shift;
734         my $client = shift;
735         my @binds = @_;
736
737         return undef unless (@binds);
738
739         my $func = 'actor.org_unit_full_path(?)';
740         $func = 'actor.org_unit_full_path(?,?)' if (@binds > 1);
741
742         my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
743         $sth->execute(@binds);
744
745         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
746
747         return undef;
748 }
749 __PACKAGE__->register_method(
750         api_name        => 'open-ils.storage.actor.org_unit.full_path',
751         api_level       => 1,
752         stream          => 1,
753         method          => 'org_unit_full_path',
754 );
755
756 sub org_unit_ancestors {
757         my $self = shift;
758         my $client = shift;
759         my $id = shift;
760
761         return undef unless ($id);
762
763         my $func = 'actor.org_unit_ancestors(?)';
764
765         my $sth = actor::org_unit->db_Main->prepare_cached(<<"  SQL");
766                 SELECT  f.*
767                   FROM  $func f
768                         JOIN actor.org_unit_type t ON (f.ou_type = t.id)
769                   ORDER BY t.depth, f.name;
770         SQL
771         $sth->execute(''.$id);
772
773         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
774
775         return undef;
776 }
777 __PACKAGE__->register_method(
778         api_name        => 'open-ils.storage.actor.org_unit.ancestors',
779         api_level       => 1,
780         stream          => 1,
781         method          => 'org_unit_ancestors',
782 );
783
784 sub org_unit_descendants {
785         my $self = shift;
786         my $client = shift;
787         my $id = shift;
788         my $depth = shift;
789
790         return undef unless ($id);
791
792         my $func = 'actor.org_unit_descendants(?)';
793         if (defined $depth) {
794                 $func = 'actor.org_unit_descendants(?,?)';
795         }
796
797         my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
798         $sth->execute(''.$id, ''.$depth) if (defined $depth);
799         $sth->execute(''.$id) unless (defined $depth);
800
801         $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
802
803         return undef;
804 }
805 __PACKAGE__->register_method(
806         api_name        => 'open-ils.storage.actor.org_unit.descendants',
807         api_level       => 1,
808         stream          => 1,
809         method          => 'org_unit_descendants',
810 );
811
812 sub fleshed_actor_stat_cat {
813         my $self = shift;
814         my $client = shift;
815         my @list = @_;
816         
817         @list = ($list[0]) unless ($self->api_name =~ /batch/o);
818
819         for my $sc (@list) {
820                 my $cat = actor::stat_cat->retrieve($sc);
821                 next unless ($cat);
822
823                 my $sc_fm = $cat->to_fieldmapper;
824                 $sc_fm->entries( [ map { $_->to_fieldmapper } $cat->entries ] );
825
826                 $client->respond( $sc_fm );
827
828         }
829
830         return undef;
831 }
832 __PACKAGE__->register_method(
833         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve',
834         api_level       => 1,
835         argc            => 1,
836         method          => 'fleshed_actor_stat_cat',
837 );
838
839 __PACKAGE__->register_method(
840         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve.batch',
841         api_level       => 1,
842         argc            => 1,
843         stream          => 1,
844         method          => 'fleshed_actor_stat_cat',
845 );
846
847 #XXX Fix stored proc calls
848 sub ranged_actor_stat_cat_all {
849         my $self = shift;
850         my $client = shift;
851         my $ou = ''.shift();
852         
853         return undef unless ($ou);
854         my $s_table = actor::stat_cat->table;
855
856         my $select = <<"        SQL";
857                 SELECT  s.*
858                   FROM  $s_table s
859                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
860                   ORDER BY name
861         SQL
862
863         $fleshed = 0;
864         $fleshed = 1 if ($self->api_name =~ /fleshed/o);
865
866         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
867         $sth->execute($ou);
868
869         for my $sc ( map { actor::stat_cat->construct($_) } $sth->fetchall_hash ) {
870                 my $sc_fm = $sc->to_fieldmapper;
871                 $sc_fm->entries(
872                         [ $self->method_lookup( 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat' )->run($ou,$sc->id) ]
873                 ) if ($fleshed);
874                 $client->respond( $sc_fm );
875         }
876
877         return undef;
878 }
879 __PACKAGE__->register_method(
880         api_name        => 'open-ils.storage.ranged.fleshed.actor.stat_cat.all',
881         api_level       => 1,
882         argc            => 1,
883         stream          => 1,
884         method          => 'ranged_actor_stat_cat_all',
885 );
886
887 __PACKAGE__->register_method(
888         api_name        => 'open-ils.storage.ranged.actor.stat_cat.all',
889         api_level       => 1,
890         argc            => 1,
891         stream          => 1,
892         method          => 'ranged_actor_stat_cat_all',
893 );
894
895 #XXX Fix stored proc calls
896 sub ranged_actor_stat_cat_entry {
897         my $self = shift;
898         my $client = shift;
899         my $ou = ''.shift();
900         my $sc = ''.shift();
901         
902         return undef unless ($ou);
903         my $s_table = actor::stat_cat_entry->table;
904
905         my $select = <<"        SQL";
906                 SELECT  s.*
907                   FROM  $s_table s
908                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
909                   WHERE stat_cat = ?
910                   ORDER BY name
911         SQL
912
913         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
914         $sth->execute($ou,$sc);
915
916         for my $sce ( map { actor::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
917                 $client->respond( $sce->to_fieldmapper );
918         }
919
920         return undef;
921 }
922 __PACKAGE__->register_method(
923         api_name        => 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat',
924         api_level       => 1,
925         stream          => 1,
926         method          => 'ranged_actor_stat_cat_entry',
927 );
928
929
930 1;