LP#1694577: tweak searching for words in report templates
[Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Reporter.pm
1 package OpenILS::Application::Reporter;
2 use OpenILS::Application;
3 use base qw/OpenILS::Application/;
4 use strict; use warnings;
5 use OpenSRF::Utils::Logger qw/$logger/;
6 use OpenILS::Utils::CStoreEditor qw/:funcs/;
7 use OpenILS::Utils::Fieldmapper;
8 use OpenILS::Application::AppUtils;
9 my $U = "OpenILS::Application::AppUtils";
10
11
12 __PACKAGE__->register_method(
13     api_name => 'open-ils.reporter.folder.create',
14     method => 'create_folder'
15 );
16
17 sub create_folder {
18     my( $self, $conn, $auth, $type, $folder ) = @_;
19
20     my $e = new_rstore_editor(xact=>1, authtoken=>$auth);
21     return $e->die_event unless $e->checkauth;
22     return $e->die_event unless $e->allowed('RUN_REPORTS');
23     return $e->die_event unless ($type ne 'template' || $e->allowed('CREATE_REPORT_TEMPLATE'));
24
25     return 0 if $folder->owner ne $e->requestor->id;
26
27     $folder->owner($e->requestor->id);
28     my $meth = "create_reporter_${type}_folder";
29     $e->$meth($folder) or return $e->die_event;
30     $e->commit;
31
32     return $folder->id;
33 }
34
35
36 __PACKAGE__->register_method(
37     api_name => 'open-ils.reporter.report.exists',
38     method => 'report_exists',
39     notes => q/
40         Returns 1 if a report with the given name and folder already exists.
41     /
42 );
43
44 sub report_exists {
45     my( $self, $conn, $auth, $report ) = @_;
46
47     my $e = new_rstore_editor(authtoken=>$auth);
48     return $e->event unless $e->checkauth;
49     return $e->event unless $e->allowed('RUN_REPORTS');
50
51     my $existing = $e->search_reporter_report(
52         {folder=>$report->folder, name=>$report->name});
53     return 1 if @$existing;
54     return 0;
55 }
56
57
58 __PACKAGE__->register_method(
59     api_name => 'open-ils.reporter.folder.visible.retrieve',
60     method => 'retrieve_visible_folders'
61 );
62
63 sub retrieve_visible_folders {
64     my( $self, $conn, $auth, $type ) = @_;
65     my $e = new_rstore_editor(authtoken=>$auth);
66     return $e->event unless $e->checkauth;
67     if($type eq 'output') {
68         return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
69     } else {
70         return $e->event unless $e->allowed('RUN_REPORTS');
71     }
72
73     my $class = 'rrf';
74     $class = 'rtf' if $type eq 'template';
75     $class = 'rof' if $type eq 'output';
76     my $flesh = {
77         flesh => 1,
78         flesh_fields => { $class => ['owner', 'share_with']},
79         order_by => { $class => 'name ASC'}
80     };
81
82     my $meth = "search_reporter_${type}_folder";
83     my $fs = $e->$meth( [{ owner => $e->requestor->id }, $flesh] );
84
85     my @orgs;
86     my $o = $U->storagereq(
87         'open-ils.storage.actor.org_unit.full_path.atomic', $e->requestor->ws_ou);
88     push( @orgs, $_->id ) for @$o;
89
90     my $fs2 = $e->$meth(
91         [
92             {
93                 shared => 't',
94                 share_with => \@orgs,
95                 owner => { '!=' => $e->requestor->id }
96             },
97             $flesh
98         ]
99     );
100     push( @$fs, @$fs2);
101     return $fs;
102 }
103
104
105
106 __PACKAGE__->register_method(
107     api_name => 'open-ils.reporter.folder_data.retrieve',
108     method => 'retrieve_folder_data'
109 );
110
111 sub retrieve_folder_data {
112     my( $self, $conn, $auth, $type, $folderid, $limit, $offset ) = @_;
113     my $e = new_rstore_editor(authtoken=>$auth);
114     return $e->event unless $e->checkauth;
115     if($type eq 'output') {
116         return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
117     } else {
118         return $e->event unless $e->allowed('RUN_REPORTS');
119     }
120     my $meth = "search_reporter_${type}";
121     my $class = 'rr';
122     $class = 'rt' if $type eq 'template';
123     my $flesh = {
124         flesh => 1,
125         flesh_fields => { $class => ['owner']},
126         order_by => { $class => 'create_time DESC'}
127     };
128     $flesh->{limit} = $limit if $limit;
129     $flesh->{offset} = $offset if $offset;
130     return $e->$meth([{ folder => $folderid }, $flesh]);
131 }
132
133 __PACKAGE__->register_method(
134     api_name => 'open-ils.reporter.schedule.retrieve_by_folder',
135     method => 'retrieve_schedules');
136 sub retrieve_schedules {
137     my( $self, $conn, $auth, $folderId, $limit, $complete ) = @_;
138     my $e = new_rstore_editor(authtoken=>$auth);
139     return $e->event unless $e->checkauth;
140     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
141
142     my $search = { folder => $folderId };
143     my $query = [
144         { folder => $folderId },
145         {
146             order_by => { rs => 'run_time DESC' } ,
147             flesh => 1,
148             flesh_fields => { rs => ['report'] }
149         }
150     ];
151
152     $query->[1]->{limit} = $limit if $limit;
153     $query->[0]->{complete_time} = undef unless $complete;
154     $query->[0]->{complete_time} = { '!=' => undef } if $complete;
155
156     return $e->search_reporter_schedule($query);
157 }
158
159 __PACKAGE__->register_method(
160     api_name => 'open-ils.reporter.schedule.retrieve',
161     method => 'retrieve_schedules');
162 sub retrieve_schedule {
163     my( $self, $conn, $auth, $sched_id ) = @_;
164     my $e = new_rstore_editor(authtoken=>$auth);
165     return $e->event unless $e->checkauth;
166     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
167     my $s = $e->retrieve_reporter_schedule($sched_id)
168         or return $e->event;
169     return $s;
170 }
171
172
173 __PACKAGE__->register_method(
174     api_name => 'open-ils.reporter.template.create',
175     method => 'create_template');
176 sub create_template {
177     my( $self, $conn, $auth, $template ) = @_;
178     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
179     return $e->die_event unless $e->checkauth;
180     return $e->die_event unless $e->allowed('RUN_REPORTS');
181     return $e->die_event unless $e->allowed('CREATE_REPORT_TEMPLATE');
182     $template->owner($e->requestor->id);
183
184     my $existing = $e->search_reporter_template( {owner=>$template->owner,
185             folder=>$template->folder, name=>$template->name},{idlist=>1});
186     return OpenILS::Event->new('REPORT_TEMPLATE_EXISTS') if @$existing;
187
188     my $tmpl = $e->create_reporter_template($template)
189         or return $e->die_event;
190     $e->commit;
191     return $tmpl;
192 }
193
194
195
196
197
198 __PACKAGE__->register_method(
199     api_name => 'open-ils.reporter.report.create',
200     method => 'create_report');
201 sub create_report {
202     my( $self, $conn, $auth, $report, $schedule ) = @_;
203     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
204     return $e->die_event unless $e->checkauth;
205     return $e->die_event unless $e->allowed('RUN_REPORTS');
206     $report->owner($e->requestor->id);
207
208     my $existing = $e->search_reporter_report( {owner=>$report->owner,
209             folder=>$report->folder, name=>$report->name},{idlist=>1});
210     return OpenILS::Event->new('REPORT_REPORT_EXISTS') if @$existing;
211
212     my $rpt = $e->create_reporter_report($report)
213         or return $e->die_event;
214     $schedule->report($rpt->id);
215     $schedule->runner($e->requestor->id);
216     $e->create_reporter_schedule($schedule) or return $e->die_event;
217     $e->commit;
218     return $rpt;
219 }
220
221
222 __PACKAGE__->register_method(
223     api_name => 'open-ils.reporter.schedule.create',
224     method => 'create_schedule');
225 sub create_schedule {
226     my( $self, $conn, $auth, $schedule ) = @_;
227     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
228     return $e->die_event unless $e->checkauth;
229     return $e->die_event unless $e->allowed('RUN_REPORTS');
230     my $sched = $e->create_reporter_schedule($schedule)
231         or return $e->die_event;
232     $e->commit;
233     return $sched;
234 }
235
236 __PACKAGE__->register_method(
237     api_name => 'open-ils.reporter.template.retrieve',
238     method => 'retrieve_template');
239 sub retrieve_template {
240     my( $self, $conn, $auth, $id ) = @_;
241     my $e = new_rstore_editor(authtoken=>$auth);
242     return $e->event unless $e->checkauth;
243     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
244     my $t = $e->retrieve_reporter_template($id)
245         or return $e->event;
246     return $t;
247 }
248
249
250 __PACKAGE__->register_method(
251     api_name => 'open-ils.reporter.report.retrieve',
252     method => 'retrieve_report');
253 sub retrieve_report {
254     my( $self, $conn, $auth, $id ) = @_;
255     my $e = new_rstore_editor(authtoken=>$auth);
256     return $e->event unless $e->checkauth;
257     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
258     my $r = $e->retrieve_reporter_report($id)
259         or return $e->event;
260     return $r;
261 }
262
263 __PACKAGE__->register_method(
264     api_name => 'open-ils.reporter.report.fleshed.retrieve',
265     method => 'retrieve_fleshed_report',
266     signature => {
267         desc => q/Returns report, fleshed with template, template.owner
268         and schedules. Fleshes report.runs() as a single-item array 
269         containing the most recently created reporter.schedule./
270     }
271 );
272 sub retrieve_fleshed_report {
273     my( $self, $conn, $auth, $id, $options ) = @_;
274     $options ||= {};
275
276     my $e = new_rstore_editor(authtoken=>$auth);
277     return $e->event unless $e->checkauth;
278     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
279     my $r = $e->retrieve_reporter_report([
280         $id, {
281         flesh => 2,
282         flesh_fields => {
283             rr => ['template'],
284             rt => ['owner']
285         }
286     }]) or return $e->event;
287
288     my $output = $e->search_reporter_schedule([
289         {report => $id},
290         {limit => 1, order_by => {rs => 'run_time DESC'}}
291     ]);
292
293     $r->runs($output);
294
295     return $r;
296 }
297
298
299
300
301 __PACKAGE__->register_method(
302     api_name => 'open-ils.reporter.template.update',
303     method => 'update_template');
304 sub update_template {
305     my( $self, $conn, $auth, $tmpl ) = @_;
306     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
307     return $e->die_event unless $e->checkauth;
308     return $e->die_event unless $e->allowed('RUN_REPORTS');
309     return $e->die_event unless $e->allowed('CREATE_REPORT_TEMPLATE');
310     my $t = $e->retrieve_reporter_template($tmpl->id)
311         or return $e->die_event;
312     return 0 if $t->owner ne $e->requestor->id;
313     $e->update_reporter_template($tmpl)
314         or return $e->die_event;
315     $e->commit;
316     return 1;
317 }
318
319
320 __PACKAGE__->register_method(
321     api_name => 'open-ils.reporter.report.update',
322     method => 'update_report');
323 sub update_report {
324     my( $self, $conn, $auth, $report ) = @_;
325     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
326     return $e->die_event unless $e->checkauth;
327     return $e->die_event unless $e->allowed('RUN_REPORTS');
328     my $r = $e->retrieve_reporter_report($report->id)
329         or return $e->die_event;
330     if( $r->owner ne $e->requestor->id ) {
331         $e->rollback;
332         return 0;
333     }
334     $e->update_reporter_report($report)
335         or return $e->die_event;
336     $e->commit;
337     return 1;
338 }
339
340
341 __PACKAGE__->register_method(
342     api_name => 'open-ils.reporter.schedule.update',
343     method => 'update_schedule');
344 sub update_schedule {
345     my( $self, $conn, $auth, $schedule ) = @_;
346     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
347     return $e->die_event unless $e->checkauth;
348     return $e->die_event unless $e->allowed('RUN_REPORTS');
349     my $s = $e->retrieve_reporter_schedule($schedule->id)
350         or return $e->die_event;
351     my $r = $e->retrieve_reporter_report($s->report)
352         or return $e->die_event;
353     if( $r->owner ne $e->requestor->id ) {
354         $e->rollback;
355         return 0;
356     }
357     $e->update_reporter_schedule($schedule)
358         or return $e->die_event;
359     $e->commit;
360     return 1;
361 }
362
363
364 __PACKAGE__->register_method(
365     api_name => 'open-ils.reporter.folder.update',
366     method => 'update_folder');
367 sub update_folder {
368     my( $self, $conn, $auth, $type, $folder ) = @_;
369     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
370     return $e->die_event unless $e->checkauth;
371     return $e->die_event unless $e->allowed('RUN_REPORTS');
372     my $meth = "retrieve_reporter_${type}_folder";
373     my $f = $e->$meth($folder->id) or return $e->die_event;
374     return 0 if $f->owner ne $e->requestor->id;
375     $meth = "update_reporter_${type}_folder";
376     $e->$meth($folder) or return $e->die_event;
377     $e->commit;
378     return 1;
379 }
380
381
382 __PACKAGE__->register_method(
383     api_name => 'open-ils.reporter.folder.delete',
384     method => 'delete_folder');
385 sub delete_folder {
386     my( $self, $conn, $auth, $type, $folderId ) = @_;
387     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
388     return $e->die_event unless $e->checkauth;
389     return $e->die_event unless $e->allowed('RUN_REPORTS');
390     my $meth = "retrieve_reporter_${type}_folder";
391     my $f = $e->$meth($folderId) or return $e->die_event;
392     return 0 if $f->owner ne $e->requestor->id;
393     $meth = "delete_reporter_${type}_folder";
394     $e->$meth($f) or return $e->die_event;
395     $e->commit;
396     return 1;
397 }
398
399
400 __PACKAGE__->register_method(
401     api_name => 'open-ils.reporter.template.delete',
402     method => 'delete_template');
403 sub delete_template {
404     my( $self, $conn, $auth, $templateId ) = @_;
405     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
406     return $e->die_event unless $e->checkauth;
407     return $e->die_event unless $e->allowed('RUN_REPORTS');
408
409     my $t = $e->retrieve_reporter_template($templateId)
410         or return $e->die_event;
411     return 0 if $t->owner ne $e->requestor->id;
412     $e->delete_reporter_template($t) or return $e->die_event;
413     $e->commit;
414     return 1;
415 }
416
417
418
419 __PACKAGE__->register_method(
420     api_name => 'open-ils.reporter.template.delete.cascade',
421     method => 'cascade_delete_template');
422
423 #__PACKAGE__->register_method(
424 #   api_name => 'open-ils.reporter.template.delete.cascade.force',
425 #   method => 'cascade_delete_template');
426
427 sub cascade_delete_template {
428     my( $self, $conn, $auth, $templateId ) = @_;
429
430     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
431     return $e->die_event unless $e->checkauth;
432     return $e->die_event unless $e->allowed('RUN_REPORTS');
433
434     my $ret = cascade_delete_template_impl(
435         $e, $e->requestor->id, $templateId, ($self->api_name =~ /force/o) );
436     return $ret if ref $ret; # some fatal event occurred
437
438     $e->rollback if $ret == 0;
439     $e->commit if $ret > 0;
440     return $ret;
441 }
442
443
444 __PACKAGE__->register_method(
445     api_name => 'open-ils.reporter.report.delete.cascade',
446     method => 'cascade_delete_report');
447
448 #__PACKAGE__->register_method(
449 #   api_name => 'open-ils.reporter.report.delete.cascade.force',
450 #   method => 'cascade_delete_report');
451
452 sub cascade_delete_report {
453     my( $self, $conn, $auth, $reportId ) = @_;
454
455     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
456     return $e->die_event unless $e->checkauth;
457     return $e->die_event unless $e->allowed('RUN_REPORTS');
458
459     my $ret = cascade_delete_report_impl($e, $e->requestor->id, $reportId);
460     return $ret if ref $ret; # some fatal event occurred
461
462     $e->rollback if $ret == 0;
463     $e->commit if $ret > 0;
464     return $ret;
465 }
466
467
468 # performs a cascading template delete
469 # returns 2 if all data was deleted
470 # returns 1 if some data was deleted
471 # returns 0 if no data was deleted
472 # returns event on error
473 sub cascade_delete_template_impl {
474     my( $e, $owner, $templateId ) = @_;
475
476     # fetch the template to delete
477     my $template = $e->search_reporter_template(
478         {id=>$templateId, owner=>$owner})->[0] or return 0;
479
480     # fetch he attached report IDs for this  owner
481     my $reports = $e->search_reporter_report(
482         {template=>$templateId, owner=>$owner},{idlist=>1});
483
484     # delete the attached reports
485     my $all_rpts_deleted = 1;
486     for my $r (@$reports) {
487         my $evt = cascade_delete_report_impl($e, $owner, $r);
488         return $evt if ref $evt;
489         $all_rpts_deleted = 0 unless $evt == 2;
490     }
491
492     # fetch all reports attached to this template that
493     # do not belong to $owner.  If there are any, we can't
494     # delete the template
495     my $alt_reports = $e->search_reporter_report(
496         {template=>$templateId, owner=>{"!=" => $owner}},{idlist=>1});
497
498     # all_rpts_deleted will be false if a report has an
499     # attached scheduled owned by a different user
500     return 1 if @$alt_reports or not $all_rpts_deleted;
501
502     $e->delete_reporter_template($template)
503         or return $e->die_event;
504     return 2;
505 }
506
507 # performs a cascading report delete
508 # returns 2 if all data was deleted
509 # returns 1 if some data was deleted
510 # returns 0 if no data was deleted
511 # returns event on error
512 sub cascade_delete_report_impl {
513     my( $e, $owner, $reportId ) = @_;
514
515     # fetch the report to delete
516     my $report = $e->search_reporter_report(
517         {id=>$reportId, owner=>$owner})->[0] or return 0;
518
519     # fetch the attached schedule IDs for this owner
520     my $scheds = $e->search_reporter_schedule(
521         {report=>$reportId, runner=>$owner},{idlist=>1});
522
523     # delete the attached schedules
524     for my $sched (@$scheds) {
525         my $evt = delete_schedule_impl($e, $sched);
526         return $evt if $evt;
527     }
528
529     # fetch all schedules attached to this report that
530     # do not belong to $owner.  If there are any, we can't
531     # delete the report
532     my $alt_scheds = $e->search_reporter_schedule(
533         {report=>$reportId, runner=>{"!=" => $owner}},{idlist=>1});
534
535     return 1 if @$alt_scheds;
536
537     $e->delete_reporter_report($report)
538         or return $e->die_event;
539
540     return 2;
541 }
542
543
544 # deletes the requested schedule
545 # returns undef on success, event on error
546 sub delete_schedule_impl {
547     my( $e, $schedId ) = @_;
548     my $s = $e->retrieve_reporter_schedule($schedId)
549         or return $e->die_event;
550     $e->delete_reporter_schedule($s) or return $e->die_event;
551     return undef;
552 }
553
554
555
556
557 __PACKAGE__->register_method(
558     api_name => 'open-ils.reporter.report.delete',
559     method => 'delete_report');
560 sub delete_report {
561     my( $self, $conn, $auth, $reportId ) = @_;
562     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
563     return $e->die_event unless $e->checkauth;
564     return $e->die_event unless $e->allowed('RUN_REPORTS');
565
566     my $t = $e->retrieve_reporter_report($reportId)
567         or return $e->die_event;
568     return 0 if $t->owner ne $e->requestor->id;
569     $e->delete_reporter_report($t) or return $e->die_event;
570     $e->commit;
571     return 1;
572 }
573
574
575 __PACKAGE__->register_method(
576     api_name => 'open-ils.reporter.schedule.delete',
577     method => 'delete_schedule');
578 sub delete_schedule {
579     my( $self, $conn, $auth, $scheduleId ) = @_;
580     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
581     return $e->die_event unless $e->checkauth;
582     return $e->die_event unless $e->allowed('RUN_REPORTS');
583
584     my $t = $e->retrieve_reporter_schedule($scheduleId)
585         or return $e->die_event;
586     return 0 if $t->runner ne $e->requestor->id;
587     $e->delete_reporter_schedule($t) or return $e->die_event;
588     $e->commit;
589     return 1;
590 }
591
592
593 __PACKAGE__->register_method(
594     api_name => 'open-ils.reporter.template_has_reports',
595     method => 'has_reports');
596 sub has_reports {
597     my( $self, $conn, $auth, $templateId ) = @_;
598     my $e = new_rstore_editor(authtoken=>$auth);
599     return $e->die_event unless $e->checkauth;
600     return $e->die_event unless $e->allowed('RUN_REPORTS');
601     my $rpts = $e->search_reporter_report({template=>$templateId},{idlist=>1});
602     return 1 if @$rpts;
603     return 0;
604 }
605
606 __PACKAGE__->register_method(
607     api_name => 'open-ils.reporter.report_has_output',
608     method => 'has_output');
609 sub has_output {
610     my( $self, $conn, $auth, $reportId ) = @_;
611     my $e = new_rstore_editor(authtoken=>$auth);
612     return $e->die_event unless $e->checkauth;
613     return $e->die_event unless $e->allowed('RUN_REPORTS');
614     my $outs = $e->search_reporter_schedule({report=>$reportId},{idlist=>1});
615     return 1 if @$outs;
616     return 0;
617 }
618
619
620
621 __PACKAGE__->register_method(
622     method => 'org_full_path',
623     api_name => 'open-ils.reporter.org_unit.full_path');
624
625 sub org_full_path {
626     my( $self, $conn, $orgid ) = @_;
627     return $U->storagereq(
628         'open-ils.storage.actor.org_unit.full_path.atomic', $orgid );
629 }
630
631
632
633
634 __PACKAGE__->register_method(
635     method => 'magic_fetch_all',
636     api_name => 'open-ils.reporter.magic_fetch');
637 sub magic_fetch_all {
638     my( $self, $conn, $auth, $args ) = @_;
639     my $e = new_editor(authtoken => $auth);
640     return $e->event unless $e->checkauth;
641     return $e->event unless $e->allowed('RUN_REPORTS');
642
643     my $hint = $$args{hint};
644     my $org_col = $$args{org_column};
645     my $orgs = $$args{org};
646
647 #   if ($orgs && !$$args{no_fetch}) {
648 #       ($orgs) = $self
649 #               ->method_lookup( 'open-ils.reporter.org_unit.full_path' )
650 #               ->run( @$orgs );
651 #       $orgs = [ map {$_->id} @$orgs ];
652 #   }
653
654     # Find the class the iplements the given hint
655     my ($class) = grep {
656         $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
657
658     return undef unless $class->Selector;
659
660     $class =~ s/Fieldmapper:://og;
661     $class =~ s/::/_/og;
662
663     my $method;
664     my $margs;
665
666     if( $org_col ) {
667         $method = "search_$class";
668         $margs = { $org_col => $orgs };
669     } else {
670         $method = "retrieve_all_$class";
671     }
672
673     $logger->info("reporter.magic_fetch => $method");
674
675     return $e->$method($margs);
676 }
677
678 __PACKAGE__->register_method(
679     method => 'search_templates',
680     api_name => 'open-ils.reporter.search.templates',
681     stream => 1
682 );
683
684 sub search_templates {
685     my ($self, $client, $auth, $query_args) = @_;
686
687     my $limit  = $query_args->{limit} || 100;
688     my $offset = $query_args->{offset} || 0;
689     my $folder = $query_args->{folder};
690     my $fields = $query_args->{fields} || ['name','description'];
691     my $query_string  = $query_args->{query};
692
693     return undef unless $query_string;
694
695     my $e = new_rstore_editor(authtoken => $auth);
696     return $e->event unless $e->checkauth;
697
698     my ($visible_folders) = $self
699         ->method_lookup('open-ils.reporter.folder.visible.retrieve')
700         ->run($auth, 'template');
701
702     my @visible_folder_ids = map { $_->id } @$visible_folders; 
703
704     return undef unless @$visible_folders;
705
706     my $query = {
707         select => {rt => ['id']},
708         from => 'rt',
709         where => {
710             folder => \@visible_folder_ids,
711             '-and' => []
712         },
713         limit => $limit,
714         offset => $offset
715     };
716
717     if ($folder) { # search request for specific folder + sub-folders
718         my ($root_folder) = grep { $_->id == $folder} @$visible_folders;
719
720         return OpenILS::Event->new('BAD_PARAMS', 
721             desc => q/Cannot search requested folder/) unless $root_folder;
722
723         # find all folders that are descendants of the selected folder.
724         my @ffilter;
725         my $finder;
726         $finder = sub {
727             my $node = shift;
728             return unless $node;
729             push(@ffilter, $node->id);
730             my @children = grep { $_->parent == $node->id } @$visible_folders;
731             $finder->($_) for @children;
732         };
733
734         $finder->($root_folder);
735         $query->{where}->{folder} = \@ffilter;
736     }
737
738     $query_string =~ s/^\s+|\s+$//gm; # remove open/trailing spaces
739     my @query_parts = split(/ +/, $query_string);
740
741     # Compile the query parts and searched fields down to a JSON-query
742     # structure like this.  Note that single-field searches have no
743     # nested -or's.
744     # where => {
745     #   -and => [
746     #       {-or => [
747     #           {$field1 => {~* => $value1}},
748     #           {$field2 => {~* => $value1}}
749     #       },
750     #       {-or => [
751     #           {$field1 => {~* => $value2}},
752     #           {$field2 => {~* => $value2}}
753     #       }
754     #   ]
755     #}
756     for my $part (@query_parts) {
757         my $subq;
758
759         if (@$fields > 1) {
760             $subq = {'-or' => []};
761             for my $field (@$fields) {
762                 push(@{$subq->{'-or'}}, {$field => {'~*' => "(^|\\m)$part"}});
763             }
764         } else {
765             $subq = {$fields->[0] => {'~*' => "(^|\\m)$part"}};
766         }
767
768         push(@{$query->{where}->{'-and'}}, $subq);
769     }
770
771     my $template_ids = $e->json_query($query);
772
773     # Flesh template owner for consistency with retrieve_folder_data
774     my $flesh = {flesh => 1, flesh_fields => {rt => ['owner']}};
775
776     $client->respond($e->retrieve_reporter_template([$_->{id}, $flesh])) 
777         for @$template_ids;
778
779     return;
780 }
781
782
783
784 1;