]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Publisher/actor.pm
Post-2.5-m1 whitespace fixup
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / 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 use DateTime::Set;
12 use DateTime::SpanSet;
13
14 my $_dt_parser = DateTime::Format::ISO8601->new;    
15
16 my $log = 'OpenSRF::Utils::Logger';
17
18 sub new_usergroup_id {
19     return actor::user->db_Main->selectrow_array("select nextval('actor.usr_usrgroup_seq'::regclass)");
20 }
21 __PACKAGE__->register_method(
22     api_name    => 'open-ils.storage.actor.user.group_id.new',
23     api_level   => 1,
24     method      => 'new_usergroup_id',
25 );
26
27 sub juv_to_adult {
28     my $self = shift;
29     my $client = shift;
30     my $adult_age = shift;
31
32     my $sql = <<"    SQL";
33             UPDATE  actor.usr
34               SET   juvenile = FALSE
35               WHERE AGE(dob) > ?::INTERVAL
36               AND juvenile IS TRUE;
37     SQL
38
39     my $sth = actor::user->db_Main->prepare_cached($sql);
40     $sth->execute($adult_age);
41
42     return $sth->rows;
43 }
44 __PACKAGE__->register_method(
45     api_name    => 'open-ils.storage.actor.user.juvenile_to_adult',
46     api_level   => 1,
47     method      => 'juv_to_adult',
48 );
49
50 sub usr_total_owed {
51     my $self = shift;
52     my $client = shift;
53     my $usr = shift;
54
55     my $sql = <<"    SQL";
56             SELECT  x.usr,
57                     SUM(COALESCE((SELECT SUM(b.amount) FROM money.billing b WHERE b.voided IS FALSE AND b.xact = x.id),0.0)) -
58                         SUM(COALESCE((SELECT SUM(p.amount) FROM money.payment p WHERE p.voided IS FALSE AND p.xact = x.id),0.0))
59               FROM  money.billable_xact x
60               WHERE x.usr = ? AND x.xact_finish IS NULL
61               GROUP BY 1
62     SQL
63
64     my (undef,$val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
65
66     return $val;
67 }
68 __PACKAGE__->register_method(
69     api_name    => 'open-ils.storage.actor.user.total_owed',
70     api_level   => 1,
71     method      => 'usr_total_owed',
72 );
73
74 sub usr_breakdown_out {
75     my $self = shift;
76     my $client = shift;
77     my $usr = shift;
78
79     $self->method_lookup('open-ils.storage.transaction.begin')->run($client);
80
81     my $out_sql = <<"    SQL";
82             SELECT  id
83               FROM  action.circulation
84               WHERE usr = ?
85                     AND checkin_time IS NULL
86                     AND (  (fine_interval >= '1 day' AND due_date >= 'today')
87                         OR (fine_interval < '1 day'  AND due_date > 'now'   ))
88                     AND (stop_fines IS NULL
89                         OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
90     SQL
91
92     my $out = actor::user->db_Main->selectcol_arrayref($out_sql, {}, $usr);
93
94     my $od_sql = <<"    SQL";
95             SELECT  id
96               FROM  action.circulation
97               WHERE usr = ?
98                     AND checkin_time IS NULL
99                     AND (  (fine_interval >= '1 day' AND due_date < 'today')
100                         OR (fine_interval < '1 day'  AND due_date < 'now'  ))
101                     AND (stop_fines IS NULL
102                         OR stop_fines NOT IN ('LOST','CLAIMSRETURNED','LONGOVERDUE'))
103     SQL
104
105     my $od = actor::user->db_Main->selectcol_arrayref($od_sql, {}, $usr);
106
107     my $lost_sql = <<"    SQL";
108             SELECT  id
109               FROM  action.circulation
110               WHERE usr = ? AND checkin_time IS NULL AND xact_finish IS NULL AND stop_fines = 'LOST'
111     SQL
112
113     my $lost = actor::user->db_Main->selectcol_arrayref($lost_sql, {}, $usr);
114
115     my $cl_sql = <<"    SQL";
116             SELECT  id
117               FROM  action.circulation
118               WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'CLAIMSRETURNED'
119     SQL
120
121     my $cl = actor::user->db_Main->selectcol_arrayref($cl_sql, {}, $usr);
122
123     my $lo_sql = <<"    SQL";
124             SELECT  id
125               FROM  action.circulation
126               WHERE usr = ? AND checkin_time IS NULL AND stop_fines = 'LONGOVERDUE'
127     SQL
128
129     my $lo = actor::user->db_Main->selectcol_arrayref($lo_sql, {}, $usr);
130
131     $self->method_lookup('open-ils.storage.transaction.rollback')->run($client);
132
133     if ($self->api_name =~/count$/o) {
134         return {    total   => scalar(@$out) + scalar(@$od) + scalar(@$lost) + scalar(@$cl) + scalar(@$lo),
135                     out     => scalar(@$out),
136                     overdue => scalar(@$od),
137                     lost    => scalar(@$lost),
138                     claims_returned => scalar(@$cl),
139                     long_overdue        => scalar(@$lo),
140         };
141     }
142
143     return {    out     => $out,
144                 overdue => $od,
145                 lost    => $lost,
146                 claims_returned => $cl,
147                 long_overdue        => $lo,
148     };
149 }
150 __PACKAGE__->register_method(
151     api_name    => 'open-ils.storage.actor.user.checked_out',
152     api_level   => 1,
153     method      => 'usr_breakdown_out',
154 );
155 __PACKAGE__->register_method(
156     api_name    => 'open-ils.storage.actor.user.checked_out.count',
157     api_level   => 1,
158     method      => 'usr_breakdown_out',
159 );
160
161 sub usr_total_out {
162     my $self = shift;
163     my $client = shift;
164     my $usr = shift;
165
166     my $sql = <<"    SQL";
167             SELECT  count(*)
168               FROM  action.circulation
169               WHERE usr = ? AND checkin_time IS NULL
170     SQL
171
172     my ($val) = actor::user->db_Main->selectrow_array($sql, {}, $usr);
173
174     return $val;
175 }
176 __PACKAGE__->register_method(
177     api_name    => 'open-ils.storage.actor.user.total_out',
178     api_level   => 1,
179     method      => 'usr_total_out',
180 );
181
182 sub calc_proximity {
183     my $self = shift;
184     my $client = shift;
185
186     local $OpenILS::Application::Storage::WRITE = 1;
187
188     my $delete_sql = <<"    SQL";
189         DELETE FROM actor.org_unit_proximity;
190     SQL
191
192     my $insert_sql = <<"    SQL";
193         INSERT INTO actor.org_unit_proximity (from_org, to_org, prox)
194             SELECT  l.id,
195                 r.id,
196                 actor.org_unit_proximity(l.id,r.id)
197               FROM  actor.org_unit l,
198                 actor.org_unit r;
199     SQL
200
201     actor::org_unit_proximity->db_Main->do($delete_sql);
202     actor::org_unit_proximity->db_Main->do($insert_sql);
203
204     return 1;
205 }
206 __PACKAGE__->register_method(
207     api_name    => 'open-ils.storage.actor.org_unit.refresh_proximity',
208     api_level   => 1,
209     method      => 'calc_proximity',
210 );
211
212 sub make_hoo_spanset {
213     my $hoo = shift;
214     return undef unless $hoo;
215
216     my $today = shift || DateTime->now;
217
218     my $tz = OpenSRF::AppSession->create('open-ils.actor')->request(
219         'open-ils.actor.ou_setting.ancestor_default' => $hoo->id.'' => 'org_unit.timezone'
220     )->gather(1) || DateTime::TimeZone->new( name => 'local' )->name;
221
222     my $current_dow = $today->day_of_week_0;
223
224     my $spanset = DateTime::SpanSet->empty_set;
225     for my $d ( 0 .. 6 ) {
226
227         my $omethod = 'dow_'.$d.'_open';
228         my $cmethod = 'dow_'.$d.'_close';
229
230         my $open = interval_to_seconds($hoo->$omethod());
231         my $close = interval_to_seconds($hoo->$cmethod());
232
233         next if ($open == $close && $open == 0);
234
235         my $dow_offset = ($d - $current_dow) * $one_day;
236         $close += $one_day if ($close <= $open);
237
238         $spanset = $spanset->union(
239             DateTime::Span->new(
240                 start => $today->clone->add( seconds => $dow_offset + $open  ),
241                 end   => $today->clone->add( seconds => $dow_offset + $close )
242             )
243         );
244     }
245
246     return $spanset->complement;
247 }
248
249 sub make_closure_spanset {
250     my $closures = shift;
251     return undef unless $closures;
252
253     my $spanset = DateTime::SpanSet->empty_set;
254     for my $k ( keys %$closures ) {
255         my $c = $$closures{$k};
256
257         $spanset = $spanset->union(
258             DateTime::Span->new(
259                 start => $_dt_parser->parse_datetime(cleanse_ISO8601($c->{close_start})),
260                 end   => $_dt_parser->parse_datetime(cleanse_ISO8601($c->{close_end}))
261             )
262         );
263     }
264
265     return $spanset;
266 }
267
268 sub new_org_closed_overlap {
269     my $self = shift;
270     my $client = shift;
271     my $ou = shift;
272     my $date = shift;
273     my $direction = shift || 0;
274     my $no_hoo = shift || 0;
275
276     return undef unless ($date && $ou);
277
278     # we're given a date and a direction, find any closures that contain the date
279     my $t = actor::org_unit::closed_date->table;
280     my $sql = <<"    SQL";
281         SELECT  *
282           FROM  $t
283           WHERE close_end > ?
284             AND org_unit = ?
285           ORDER BY close_start ASC, close_end DESC
286           LIMIT 1
287     SQL
288
289     $date = cleanse_ISO8601($date);
290
291     my $target_date = $_dt_parser->parse_datetime( $date );
292     my ($begin, $end) = ($target_date, $target_date);
293
294     # create a spanset from the closures that contain the $date
295     my $closure_spanset = make_closure_spanset(
296         actor::org_unit::closed_date->db_Main->selectall_hashref( $sql, 'id', {}, $date, $ou )
297     );
298
299     if ($closure_spanset && $closure_spanset->intersects( $target_date )) {
300         my $closure_intersection = $closure_spanset->intersection( $target_date );
301         $begin = $closure_intersection->min;
302         $end = $closure_intersection->max;
303
304         if ( $direction <= 0 ) {
305             $begin->subtract( minutes => 1 );
306
307             while ( my $_b = new_org_closed_overlap($self, $client, $ou, $begin->strftime('%FT%T%z'), -1, 1 ) ) {
308                 $begin = $_dt_parser->parse_datetime( cleanse_ISO8601($_b->{start}) );
309             }
310         }
311
312         if ( $direction >= 0 ) {
313             $end->add( minutes => 1 );
314
315             while ( my $_a = new_org_closed_overlap($self, $client, $ou, $end->strftime('%FT%T%z'), 1, 1 ) ) {
316                 $end = $_dt_parser->parse_datetime( cleanse_ISO8601($_a->{end}) );
317             }
318         }
319     }
320
321     if ( !$no_hoo ) {
322
323         my $begin_hoo = make_hoo_spanset(actor::org_unit::hours_of_operation->retrieve($ou), $begin);
324         my $end_hoo   = make_hoo_spanset(actor::org_unit::hours_of_operation->retrieve($ou), $end  );
325
326
327         if ( $begin_hoo && $direction <= 0 && $begin_hoo->intersects($begin) ) {
328             my $hoo_intersection = $begin_hoo->intersection( $begin );
329             $begin = $hoo_intersection->min;
330             $begin->subtract( minutes => 1 );
331
332             while ( my $_b = new_org_closed_overlap($self, $client, $ou, $begin->strftime('%FT%T%z'), -1 ) ) {
333                 $begin = $_dt_parser->parse_datetime( cleanse_ISO8601($_b->{start}) );
334             }
335         }
336     
337         if ( $end_hoo && $direction >= 0 && $end_hoo->intersects($end) ) {
338             my $hoo_intersection = $end_hoo->intersection( $end );
339             $end = $hoo_intersection->max;
340             $end->add( minutes => 1 );
341
342
343             while ( my $_b = new_org_closed_overlap($self, $client, $ou, $end->strftime('%FT%T%z'), -1 ) ) {
344                 $end = $_dt_parser->parse_datetime( cleanse_ISO8601($_b->{end}) );
345             }
346         }
347     }
348
349     my $start = $begin->strftime('%FT%T%z');
350     my $stop = $end->strftime('%FT%T%z');
351
352     return undef if ($start eq $stop);
353     return { start => $start, end => $stop };
354 }
355 __PACKAGE__->register_method(
356     api_name    => 'open-ils.storage.actor.org_unit.closed_date.overlap',
357     api_level   => 0,
358     method      => 'new_org_closed_overlap',
359 );
360
361 sub org_closed_overlap {
362     my $self = shift;
363     my $client = shift;
364     my $ou = shift;
365     my $date = shift;
366     my $direction = shift || 0;
367     my $no_hoo = shift || 0;
368
369     return undef unless ($date && $ou);
370
371     my $t = actor::org_unit::closed_date->table;
372     my $sql = <<"    SQL";
373         SELECT  *
374           FROM  $t
375           WHERE ? between close_start and close_end
376             AND org_unit = ?
377           ORDER BY close_start ASC, close_end DESC
378           LIMIT 1
379     SQL
380
381     $date = cleanse_ISO8601($date);
382     my ($begin, $end) = ($date,$date);
383
384     my $hoo = actor::org_unit::hours_of_operation->retrieve($ou);
385
386     if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $date, $ou )) {
387         $begin = cleanse_ISO8601($closure->{close_start});
388         $end = cleanse_ISO8601($closure->{close_end});
389
390         if ( $direction <= 0 ) {
391             $before = $_dt_parser->parse_datetime( $begin );
392             $before->subtract( minutes => 1 );
393
394             while ( my $_b = org_closed_overlap($self, $client, $ou, $before->strftime('%FT%T%z'), -1, 1 ) ) {
395                 $before = $_dt_parser->parse_datetime( cleanse_ISO8601($_b->{start}) );
396             }
397             $begin = cleanse_ISO8601($before->strftime('%FT%T%z'));
398         }
399
400         if ( $direction >= 0 ) {
401             $after = $_dt_parser->parse_datetime( $end );
402             $after->add( minutes => 1 );
403
404             while ( my $_a = org_closed_overlap($self, $client, $ou, $after->strftime('%FT%T%z'), 1, 1 ) ) {
405                 $after = $_dt_parser->parse_datetime( cleanse_ISO8601($_a->{end}) );
406             }
407             $end = cleanse_ISO8601($after->strftime('%FT%T%z'));
408         }
409     }
410
411     if ( !$no_hoo ) {
412         if ( $hoo ) {
413
414             if ( $direction <= 0 ) {
415                 my $begin_dow = $_dt_parser->parse_datetime( $begin )->day_of_week_0;
416                 my $begin_open_meth = "dow_".$begin_dow."_open";
417                 my $begin_close_meth = "dow_".$begin_dow."_close";
418
419                 my $count = 1;
420                 while ($hoo->$begin_open_meth eq '00:00:00' and $hoo->$begin_close_meth eq '00:00:00') {
421                     $begin = cleanse_ISO8601($_dt_parser->parse_datetime( $begin )->subtract( days => 1)->strftime('%FT%T%z'));
422                     $begin_dow++;
423                     $begin_dow %= 7;
424                     $count++;
425                     last if ($count > 6);
426                     $begin_open_meth = "dow_".$begin_dow."_open";
427                     $begin_close_meth = "dow_".$begin_dow."_close";
428                 }
429
430                 if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $begin, $ou )) {
431                     $before = $_dt_parser->parse_datetime( $begin );
432                     $before->subtract( minutes => 1 );
433                     while ( my $_b = org_closed_overlap($self, $client, $ou, $before->strftime('%FT%T%z'), -1 ) ) {
434                         $before = $_dt_parser->parse_datetime( cleanse_ISO8601($_b->{start}) );
435                     }
436                 }
437             }
438     
439             if ( $direction >= 0 ) {
440                 my $end_dow = $_dt_parser->parse_datetime( $end )->day_of_week_0;
441                 my $end_open_meth = "dow_".$end_dow."_open";
442                 my $end_close_meth = "dow_".$end_dow."_close";
443     
444                 $count = 1;
445                 while ($hoo->$end_open_meth eq '00:00:00' and $hoo->$end_close_meth eq '00:00:00') {
446                     $end = cleanse_ISO8601($_dt_parser->parse_datetime( $end )->add( days => 1)->strftime('%FT%T%z'));
447                     $end_dow++;
448                     $end_dow %= 7;
449                     $count++;
450                     last if ($count > 6);
451                     $end_open_meth = "dow_".$end_dow."_open";
452                     $end_close_meth = "dow_".$end_dow."_close";
453                 }
454
455                 if (my $closure = actor::org_unit::closed_date->db_Main->selectrow_hashref( $sql, {}, $end, $ou )) {
456                     $after = $_dt_parser->parse_datetime( $end );
457                     $after->add( minutes => 1 );
458
459                     while ( my $_a = org_closed_overlap($self, $client, $ou, $after->strftime('%FT%T%z'), 1 ) ) {
460                         $after = $_dt_parser->parse_datetime( cleanse_ISO8601($_a->{end}) );
461                     }
462                     $end = cleanse_ISO8601($after->strftime('%FT%T%z'));
463                 }
464             }
465
466         }
467     }
468
469     if ($begin eq $date && $end eq $date) {
470         return undef;
471     }
472
473     return { start => $begin, end => $end };
474 }
475 __PACKAGE__->register_method(
476     api_name    => 'open-ils.storage.actor.org_unit.closed_date.overlap',
477     api_level   => 1,
478     method      => 'org_closed_overlap',
479 );
480
481 sub user_by_barcode {
482     my $self = shift;
483     my $client = shift;
484     my @barcodes = shift;
485
486     return undef unless @barcodes;
487
488     for my $card ( actor::card->search( { barcode => @barcodes } ) ) {
489         next unless $card;
490         if (@barcodes == 1) {
491             return $card->usr->to_fieldmapper;
492         }
493         $client->respond( $card->usr->to_fieldmapper);
494     }
495     return undef;
496 }
497 __PACKAGE__->register_method(
498     api_name    => 'open-ils.storage.direct.actor.user.search.barcode',
499     api_level   => 1,
500     method      => 'user_by_barcode',
501     stream      => 1,
502     cachable    => 1,
503 );
504
505 sub lost_barcodes {
506     my $self = shift;
507     my $client = shift;
508
509     my $c = actor::card->table;
510     my $p = actor::user->table;
511
512     my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.card <> c.id";
513
514     my $list = actor::user->db_Main->selectcol_arrayref($sql);
515     for my $bc ( @$list ) {
516         $client->respond($bc);
517     }
518     return undef;
519 }
520 __PACKAGE__->register_method(
521     api_name    => 'open-ils.storage.actor.user.lost_barcodes',
522     api_level   => 1,
523     stream      => 1,
524     method      => 'lost_barcodes',
525     signature    => <<'    NOTE',
526         Returns an array of barcodes that belong to lost cards.
527         @return array of barcodes
528     NOTE
529 );
530
531 sub expired_barcodes {
532     my $self = shift;
533     my $client = shift;
534
535     my $c = actor::card->table;
536     my $p = actor::user->table;
537
538     my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.expire_date < CURRENT_DATE";
539
540     my $list = actor::user->db_Main->selectcol_arrayref($sql);
541     for my $bc ( @$list ) {
542         $client->respond($bc);
543     }
544     return undef;
545 }
546 __PACKAGE__->register_method(
547     api_name    => 'open-ils.storage.actor.user.expired_barcodes',
548     api_level   => 1,
549     stream      => 1,
550     method      => 'expired_barcodes',
551     signature    => <<'    NOTE',
552         Returns an array of barcodes that are currently expired.
553         @return array of barcodes
554     NOTE
555 );
556
557 sub barred_barcodes {
558     my $self = shift;
559     my $client = shift;
560
561     my $c = actor::card->table;
562     my $p = actor::user->table;
563
564     my $sql = "SELECT c.barcode FROM $c c JOIN $p p ON (c.usr = p.id) WHERE p.barred IS TRUE";
565
566     my $list = actor::user->db_Main->selectcol_arrayref($sql);
567     for my $bc ( @$list ) {
568         $client->respond($bc);
569     }
570     return undef;
571 }
572 __PACKAGE__->register_method(
573     api_name    => 'open-ils.storage.actor.user.barred_barcodes',
574     api_level   => 1,
575     stream      => 1,
576     method      => 'barred_barcodes',
577     signature    => <<'    NOTE',
578         Returns an array of barcodes that are currently barred.
579         @return array of barcodes
580     NOTE
581 );
582
583 sub penalized_barcodes {
584     my $self = shift;
585     my $client = shift;
586
587     my $c = actor::card->table;
588     my $p = actor::user_standing_penalty->table;
589
590     my $sql = <<"    SQL";
591         SELECT  DISTINCT c.barcode
592           FROM  $c c
593             JOIN $p p USING (usr)
594             JOIN config.standing_penalty csp ON (csp.id = p.standing_penalty)
595           WHERE csp.block_list IS NOT NULL
596             AND p.set_date < CURRENT_DATE
597             AND (p.stop_date IS NULL OR p.stop_date > CURRENT_DATE);
598     SQL
599
600     my $list = actor::user->db_Main->selectcol_arrayref($sql);
601     for my $bc ( @$list ) {
602         $client->respond($bc);
603     }
604     return undef;
605 }
606 __PACKAGE__->register_method(
607     api_name    => 'open-ils.storage.actor.user.penalized_barcodes',
608     api_level   => 1,
609     stream      => 1,
610     method      => 'penalized_barcodes',
611     signature    => <<'    NOTE',
612         Returns an array of barcodes that have blocking penalties.
613         @return array of barcodes
614     NOTE
615 );
616
617 sub _clean_regex_chars {
618     my ($search) = @_;
619
620     # Escape metacharacters for SIMILAR TO 
621     # (http://www.postgresql.org/docs/8.4/interactive/functions-matching.html)
622     $search =~ s/\_/\\_/g;
623     $search =~ s/\%/\\%/g;
624     $search =~ s/\|/\\|/g;
625     $search =~ s/\*/\\*/g;
626     $search =~ s/\+/\\+/g;
627     $search =~ s/\[/\\[/g;
628     $search =~ s/\]/\\]/g;
629     $search =~ s/\(/\\(/g;
630     $search =~ s/\)/\\)/g;
631
632     return $search;
633 }
634
635 sub patron_search {
636     my $self = shift;
637     my $client = shift;
638     my $search = shift;
639     my $limit = shift || 1000;
640     my $sort = shift;
641     my $inactive = shift;
642     my $ws_ou = shift;
643     my $search_org = shift || $ws_ou;
644     my $opt_boundary = shift || 0;
645
646     my $penalty_sort = 0;
647
648     my $strict_opt_in = OpenSRF::Utils::SettingsClient->new->config_value( share => user => 'opt_in' );
649
650     $sort = ['family_name','first_given_name'] unless ($$sort[0]);
651     push @$sort,'id';
652
653     if ($$sort[0] eq 'penalties') {
654         shift @$sort;
655         $penalty_sort = 1;
656     }
657
658     # group 0 = user
659     # group 1 = address
660     # group 2 = phone, ident
661     # group 3 = barcode
662
663     my $usr = join ' AND ', map { "evergreen.lowercase(CAST($_ AS text)) ~ ?" } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
664     my @usrv = map { "^" . _clean_regex_chars($$search{$_}{value}) } grep { ''.$$search{$_}{group} eq '0' } keys %$search;
665
666     my $addr = join ' AND ', map { "evergreen.lowercase(CAST($_ AS text)) ~ ?" } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
667     my @addrv = map { "^" . _clean_regex_chars($$search{$_}{value}) } grep { ''.$$search{$_}{group} eq '1' } keys %$search;
668
669     my $pv = _clean_regex_chars($$search{phone}{value});
670     my $iv = _clean_regex_chars($$search{ident}{value});
671     my $nv = _clean_regex_chars($$search{name}{value});
672     my $cv = _clean_regex_chars($$search{card}{value});
673
674     my $card = '';
675     if ($cv) {
676         $card = 'JOIN (SELECT DISTINCT usr FROM actor.card WHERE evergreen.lowercase(barcode) LIKE ?||\'%\') AS card ON (card.usr = users.id)';
677         unshift(@usrv, $cv);
678     }
679
680     my $phone = '';
681     my @ps;
682     my @phonev;
683     if ($pv) {
684         for my $p ( qw/day_phone evening_phone other_phone/ ) {
685             if ($pv =~ /^\d+$/) {
686                 push @ps, "evergreen.lowercase(REGEXP_REPLACE($p, '[^0-9]', '', 'g')) ~ ?";
687             } else {
688                 push @ps, "evergreen.lowercase($p) ~ ?";
689             }
690             push @phonev, "^$pv";
691         }
692         $phone = '(' . join(' OR ', @ps) . ')';
693     }
694
695     my $ident = '';
696     my @is;
697     my @identv;
698     if ($iv) {
699         for my $i ( qw/ident_value ident_value2/ ) {
700             push @is, "evergreen.lowercase($i) ~ ?";
701             push @identv, "^$iv";
702         }
703         $ident = '(' . join(' OR ', @is) . ')';
704     }
705
706     my $name = '';
707     my @ns;
708     my @namev;
709     if (0 && $nv) {
710         for my $n ( qw/first_given_name second_given_name family_name/ ) {
711             push @ns, "evergreen.lowercase($n) ~ ?";
712             push @namev, "^$nv";
713         }
714         $name = '(' . join(' OR ', @ns) . ')';
715     }
716
717     my $usr_where = join ' AND ', grep { $_ } ($usr,$phone,$ident,$name);
718     my $addr_where = $addr;
719
720
721     my $u_table = actor::user->table;
722     my $a_table = actor::user_address->table;
723     my $opt_in_table = actor::usr_org_unit_opt_in->table;
724     my $ou_table = actor::org_unit->table;
725
726     my $u_select = "SELECT id as id FROM $u_table u WHERE $usr_where";
727     my $a_select = "SELECT u.id as id FROM $a_table a JOIN $u_table u ON (u.mailing_address = a.id OR u.billing_address = a.id) WHERE $addr_where";
728
729     my $clone_select = '';
730
731     #$clone_select = "JOIN (SELECT cu.id as id FROM $a_table ca ".
732     #          "JOIN $u_table cu ON (cu.mailing_address = ca.id OR cu.billing_address = ca.id) ".
733     #          "WHERE $addr_where) AS clone ON (clone.id = users.id)" if ($addr_where);
734
735     my $select = '';
736     if ($usr_where) {
737         if ($addr_where) {
738             $select = "$u_select INTERSECT $a_select";
739         } else {
740             $select = $u_select;
741         }
742     } elsif ($addr_where) {
743         $select = "$a_select";
744     }
745
746     return undef if (!$select && !$card);
747
748     my $order_by = join ', ', map { 'evergreen.lowercase(CAST(users.'. (split / /,$_)[0] . ' AS text)) ' . (split / /,$_)[1] } @$sort;
749     my $distinct_list = join ', ', map { 'evergreen.lowercase(CAST(users.'. (split / /,$_)[0] . ' AS text))' } @$sort;
750     my $group_list = $distinct_list;
751
752     if ($inactive) {
753         $inactive = '';
754     } else {
755         $inactive = 'AND users.active = TRUE';
756     }
757
758     if (!$ws_ou) {  # XXX This should be required!!
759         $ws_ou = actor::org_unit->search( { parent_ou => undef } )->next->id;
760     }
761
762     my $descendants = "actor.org_unit_descendants($search_org)";
763
764     my $opt_in_where = '';
765     if (lc($strict_opt_in) eq 'true') {
766         $opt_in_where = "AND (";
767         $opt_in_where .= "EXISTS (select id FROM $opt_in_table ";
768         $opt_in_where .= " WHERE org_unit in (select (actor.org_unit_ancestors($ws_ou)).id)";
769         $opt_in_where .= " AND usr = users.id) ";
770         $opt_in_where .= "OR";
771         $opt_in_where .= " users.home_ou IN (select (actor.org_unit_descendants($ws_ou,$opt_boundary)).id))";
772     }
773
774     my $penalty_join = '';
775     if ($penalty_sort) {
776         $distinct_list = 'COUNT(penalties.id), ' . $distinct_list;
777         $order_by = 'COUNT(penalties.id) DESC, ' . $order_by;
778         unshift @$sort, 'COUNT(penalties.id)';
779         $penalty_join = <<"        SQL";
780             LEFT JOIN actor.usr_standing_penalty penalties
781                 ON (users.id = penalties.usr AND (penalties.stop_date IS NULL OR penalties.stop_date > NOW()))
782         SQL
783     }
784
785     $select = "JOIN ($select) AS search ON (search.id = users.id)" if ($select);
786     $select = <<"    SQL";
787         SELECT  $distinct_list
788           FROM  $u_table AS users $card
789             JOIN $descendants d ON (d.id = users.home_ou)
790             $select
791             $clone_select
792             $penalty_join
793           WHERE users.deleted = FALSE
794             $inactive
795             $opt_in_where
796           GROUP BY $group_list
797           ORDER BY $order_by
798           LIMIT $limit
799     SQL
800
801     return actor::user->db_Main->selectcol_arrayref($select, {Columns=>[scalar(@$sort)]}, map {lc($_)} (@usrv,@phonev,@identv,@namev,@addrv));
802 }
803 __PACKAGE__->register_method(
804     api_name    => 'open-ils.storage.actor.user.crazy_search',
805     api_level   => 1,
806     method      => 'patron_search',
807 );
808
809 sub org_unit_list {
810     my $self = shift;
811     my $client = shift;
812
813     my $select =<<"    SQL";
814     SELECT  *
815       FROM  actor.org_unit
816       ORDER BY CASE WHEN parent_ou IS NULL THEN 0 ELSE 1 END, name;
817     SQL
818
819     my $sth = actor::org_unit->db_Main->prepare_cached($select);
820     $sth->execute;
821
822     $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
823
824     return undef;
825 }
826 __PACKAGE__->register_method(
827     api_name    => 'open-ils.storage.direct.actor.org_unit.retrieve.all',
828     api_level   => 1,
829     stream      => 1,
830     method      => 'org_unit_list',
831 );
832
833 sub org_unit_type_list {
834     my $self = shift;
835     my $client = shift;
836
837     my $select =<<"    SQL";
838     SELECT  *
839       FROM  actor.org_unit_type
840       ORDER BY depth, name;
841     SQL
842
843     my $sth = actor::org_unit_type->db_Main->prepare_cached($select);
844     $sth->execute;
845
846     $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit_type->construct($_) } $sth->fetchall_hash );
847
848     return undef;
849 }
850 __PACKAGE__->register_method(
851     api_name    => 'open-ils.storage.direct.actor.org_unit_type.retrieve.all',
852     api_level   => 1,
853     stream      => 1,
854     method      => 'org_unit_type_list',
855 );
856
857 sub org_unit_full_path {
858     my $self = shift;
859     my $client = shift;
860     my @binds = @_;
861
862     return undef unless (@binds);
863
864     my $func = 'actor.org_unit_full_path(?)';
865     $func = 'actor.org_unit_full_path(?,?)' if (@binds > 1);
866
867     my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
868     $sth->execute(@binds);
869
870     $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
871
872     return undef;
873 }
874 __PACKAGE__->register_method(
875     api_name    => 'open-ils.storage.actor.org_unit.full_path',
876     api_level   => 1,
877     stream      => 1,
878     method      => 'org_unit_full_path',
879 );
880
881 sub org_unit_ancestors {
882     my $self = shift;
883     my $client = shift;
884     my $id = shift;
885
886     return undef unless ($id);
887
888     my $func = 'actor.org_unit_ancestors(?)';
889
890     my $sth = actor::org_unit->db_Main->prepare_cached(<<"    SQL");
891         SELECT  f.*
892           FROM  $func f
893             JOIN actor.org_unit_type t ON (f.ou_type = t.id)
894           ORDER BY t.depth, f.name;
895     SQL
896     $sth->execute(''.$id);
897
898     $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
899
900     return undef;
901 }
902 __PACKAGE__->register_method(
903     api_name    => 'open-ils.storage.actor.org_unit.ancestors',
904     api_level   => 1,
905     stream      => 1,
906     method      => 'org_unit_ancestors',
907 );
908
909 sub org_unit_descendants {
910     my $self = shift;
911     my $client = shift;
912     my $id = shift;
913     my $depth = shift;
914
915     return undef unless ($id);
916
917     my $func = 'actor.org_unit_descendants(?)';
918     if (defined $depth) {
919         $func = 'actor.org_unit_descendants(?,?)';
920     }
921
922     my $sth = actor::org_unit->db_Main->prepare_cached("SELECT * FROM $func");
923     $sth->execute(''.$id, ''.$depth) if (defined $depth);
924     $sth->execute(''.$id) unless (defined $depth);
925
926     $client->respond( $_->to_fieldmapper ) for ( map { actor::org_unit->construct($_) } $sth->fetchall_hash );
927
928     return undef;
929 }
930 __PACKAGE__->register_method(
931     api_name    => 'open-ils.storage.actor.org_unit.descendants',
932     api_level   => 1,
933     stream      => 1,
934     method      => 'org_unit_descendants',
935 );
936
937 sub fleshed_actor_stat_cat {
938         my $self = shift;
939         my $client = shift;
940         my @list = @_;
941         
942     @list = ($list[0]) unless ($self->api_name =~ /batch/o);
943
944     for my $sc (@list) {
945         my $cat = actor::stat_cat->retrieve($sc);
946         next unless ($cat);
947
948         my $sc_fm = $cat->to_fieldmapper;
949         $sc_fm->entries( [ map { $_->to_fieldmapper } $cat->entries ] );
950         $sc_fm->default_entries( [ map { $_->to_fieldmapper } $cat->default_entries ] );
951
952         $client->respond( $sc_fm );
953
954     }
955
956     return undef;
957 }
958 __PACKAGE__->register_method(
959         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve',
960         api_level       => 1,
961     argc        => 1,
962         method          => 'fleshed_actor_stat_cat',
963 );
964
965 __PACKAGE__->register_method(
966         api_name        => 'open-ils.storage.fleshed.actor.stat_cat.retrieve.batch',
967         api_level       => 1,
968     argc        => 1,
969         stream          => 1,
970         method          => 'fleshed_actor_stat_cat',
971 );
972
973 #XXX Fix stored proc calls
974 sub ranged_actor_stat_cat_all {
975         my $self = shift;
976         my $client = shift;
977         my $ou = ''.shift();
978         
979         return undef unless ($ou);
980         my $s_table = actor::stat_cat->table;
981
982         my $select = <<"        SQL";
983                 SELECT  s.*
984                   FROM  $s_table s
985                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
986           ORDER BY name
987         SQL
988
989     $fleshed = 0;
990     $fleshed = 1 if ($self->api_name =~ /fleshed/o);
991
992         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
993         $sth->execute($ou);
994
995         for my $sc ( map { actor::stat_cat->construct($_) } $sth->fetchall_hash ) {
996         my $sc_fm = $sc->to_fieldmapper;
997         $sc_fm->entries(
998             [ $self->method_lookup( 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat' )->run($ou,$sc->id) ]
999         ) if ($fleshed);
1000         $sc_fm->default_entries(
1001             [ $self->method_lookup( 'open-ils.storage.actor.stat_cat_entry_default.ancestor.retrieve' )->run($ou,$sc->id) ]
1002         ) if ($fleshed);
1003         $client->respond( $sc_fm );
1004     }
1005
1006         return undef;
1007 }
1008 __PACKAGE__->register_method(
1009         api_name        => 'open-ils.storage.ranged.fleshed.actor.stat_cat.all',
1010         api_level       => 1,
1011     argc        => 1,
1012         stream          => 1,
1013         method          => 'ranged_actor_stat_cat_all',
1014 );
1015
1016 __PACKAGE__->register_method(
1017         api_name        => 'open-ils.storage.ranged.actor.stat_cat.all',
1018         api_level       => 1,
1019     argc        => 1,
1020         stream          => 1,
1021         method          => 'ranged_actor_stat_cat_all',
1022 );
1023
1024 #XXX Fix stored proc calls
1025 sub ranged_actor_stat_cat_entry {
1026         my $self = shift;
1027         my $client = shift;
1028         my $ou = ''.shift();
1029         my $sc = ''.shift();
1030         
1031         return undef unless ($ou);
1032         my $s_table = actor::stat_cat_entry->table;
1033
1034         my $select = <<"        SQL";
1035                 SELECT  s.*
1036                   FROM  $s_table s
1037                         JOIN actor.org_unit_full_path(?) p ON (p.id = s.owner)
1038           WHERE stat_cat = ?
1039           ORDER BY name
1040         SQL
1041
1042         my $sth = actor::stat_cat->db_Main->prepare_cached($select);
1043         $sth->execute($ou,$sc);
1044
1045         for my $sce ( map { actor::stat_cat_entry->construct($_) } $sth->fetchall_hash ) {
1046         my $sce_fm = $sce->to_fieldmapper;
1047         $client->respond( $sce_fm );
1048     }
1049
1050         return undef;
1051 }
1052 __PACKAGE__->register_method(
1053         api_name        => 'open-ils.storage.ranged.actor.stat_cat_entry.search.stat_cat',
1054         api_level       => 1,
1055         stream          => 1,
1056         method          => 'ranged_actor_stat_cat_entry',
1057 );
1058
1059 sub actor_stat_cat_entry_default {
1060     my $self = shift;
1061     my $client = shift;
1062     my $ou = ''.shift();
1063     my $sc = ''.shift();
1064         
1065     return undef unless ($ou);
1066     my $s_table = actor::stat_cat_entry_default->table;
1067
1068     my $select = <<"    SQL";
1069          SELECT  s.*
1070          FROM  $s_table s
1071          WHERE owner = ? AND stat_cat = ?
1072     SQL
1073
1074     my $sth = actor::stat_cat->db_Main->prepare_cached($select);
1075     $sth->execute($ou,$sc);
1076
1077     for my $sced ( map { actor::stat_cat_entry_default->construct($_) } $sth->fetchall_hash ) {
1078         $client->respond( $sced->to_fieldmapper );
1079     }
1080
1081     return undef;
1082 }
1083 __PACKAGE__->register_method(
1084     api_name        => 'open-ils.storage.actor.stat_cat_entry_default.retrieve',
1085     api_level       => 1,
1086     stream          => 1,
1087     method          => 'actor_stat_cat_entry_default',
1088 );
1089
1090 sub actor_stat_cat_entry_default_ancestor {
1091     my $self = shift;
1092     my $client = shift;
1093     my $ou = ''.shift();
1094     my $sc = ''.shift();
1095         
1096     return undef unless ($ou);
1097     my $s_table = actor::stat_cat_entry_default->table;
1098
1099     my $select = <<"    SQL";
1100         SELECT  s.*
1101         FROM  $s_table s
1102         JOIN actor.org_unit_ancestors(?) p ON (p.id = s.owner)
1103         WHERE stat_cat = ?
1104     SQL
1105
1106     my $sth = actor::stat_cat->db_Main->prepare_cached($select);
1107     $sth->execute($ou,$sc);
1108
1109     my @sced =  map { actor::stat_cat_entry_default->construct($_) } $sth->fetchall_hash;
1110
1111     my $ancestor_sced = pop @sced;
1112
1113     $client->respond( $ancestor_sced->to_fieldmapper ) if $ancestor_sced;
1114
1115     return undef;
1116 }
1117 __PACKAGE__->register_method(
1118     api_name        => 'open-ils.storage.actor.stat_cat_entry_default.ancestor.retrieve',
1119     api_level       => 1,
1120     stream          => 1,
1121     method          => 'actor_stat_cat_entry_default_ancestor',
1122 );
1123
1124 1;