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";
12 __PACKAGE__->register_method(
13 api_name => 'open-ils.reporter.folder.create',
14 method => 'create_folder'
18 my( $self, $conn, $auth, $type, $folder ) = @_;
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'));
25 return 0 if $folder->owner ne $e->requestor->id;
27 $folder->owner($e->requestor->id);
28 my $meth = "create_reporter_${type}_folder";
29 $e->$meth($folder) or return $e->die_event;
36 __PACKAGE__->register_method(
37 api_name => 'open-ils.reporter.report.exists',
38 method => 'report_exists',
40 Returns 1 if a report with the given name and folder already exists.
45 my( $self, $conn, $auth, $report ) = @_;
47 my $e = new_rstore_editor(authtoken=>$auth);
48 return $e->event unless $e->checkauth;
49 return $e->event unless $e->allowed('RUN_REPORTS');
51 my $existing = $e->search_reporter_report(
52 {folder=>$report->folder, name=>$report->name});
53 return 1 if @$existing;
58 __PACKAGE__->register_method(
59 api_name => 'open-ils.reporter.folder.visible.retrieve',
60 method => 'retrieve_visible_folders'
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']);
70 return $e->event unless $e->allowed('RUN_REPORTS');
74 $class = 'rtf' if $type eq 'template';
75 $class = 'rof' if $type eq 'output';
78 flesh_fields => { $class => ['owner', 'share_with']},
79 order_by => { $class => 'name ASC'}
82 my $meth = "search_reporter_${type}_folder";
83 my $fs = $e->$meth( [{ owner => $e->requestor->id }, $flesh] );
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;
95 owner => { '!=' => $e->requestor->id }
106 __PACKAGE__->register_method(
107 api_name => 'open-ils.reporter.folder_data.retrieve',
108 method => 'retrieve_folder_data'
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']);
118 return $e->event unless $e->allowed('RUN_REPORTS');
120 my $meth = "search_reporter_${type}";
122 $class = 'rt' if $type eq 'template';
125 flesh_fields => { $class => ['owner']},
126 order_by => { $class => 'create_time DESC'}
128 $flesh->{limit} = $limit if $limit;
129 $flesh->{offset} = $offset if $offset;
130 return $e->$meth([{ folder => $folderid }, $flesh]);
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']);
142 my $search = { folder => $folderId };
144 { folder => $folderId },
146 order_by => { rs => 'run_time DESC' } ,
148 flesh_fields => { rs => ['report'] }
152 $query->[1]->{limit} = $limit if $limit;
153 $query->[0]->{complete_time} = undef unless $complete;
154 $query->[0]->{complete_time} = { '!=' => undef } if $complete;
156 return $e->search_reporter_schedule($query);
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)
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);
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;
188 my $tmpl = $e->create_reporter_template($template)
189 or return $e->die_event;
198 __PACKAGE__->register_method(
199 api_name => 'open-ils.reporter.report.create',
200 method => '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);
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;
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;
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;
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)
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)
263 __PACKAGE__->register_method(
264 api_name => 'open-ils.reporter.report.fleshed.retrieve',
265 method => 'retrieve_fleshed_report',
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./
272 sub retrieve_fleshed_report {
273 my( $self, $conn, $auth, $id, $options ) = @_;
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([
286 }]) or return $e->event;
288 my $output = $e->search_reporter_schedule([
290 {limit => 1, order_by => {rs => 'run_time DESC'}}
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;
320 __PACKAGE__->register_method(
321 api_name => 'open-ils.reporter.report.update',
322 method => '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 ) {
334 $e->update_reporter_report($report)
335 or return $e->die_event;
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 ) {
357 $e->update_reporter_schedule($schedule)
358 or return $e->die_event;
364 __PACKAGE__->register_method(
365 api_name => 'open-ils.reporter.folder.update',
366 method => '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;
382 __PACKAGE__->register_method(
383 api_name => 'open-ils.reporter.folder.delete',
384 method => '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;
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');
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;
419 __PACKAGE__->register_method(
420 api_name => 'open-ils.reporter.template.delete.cascade',
421 method => 'cascade_delete_template');
423 #__PACKAGE__->register_method(
424 # api_name => 'open-ils.reporter.template.delete.cascade.force',
425 # method => 'cascade_delete_template');
427 sub cascade_delete_template {
428 my( $self, $conn, $auth, $templateId ) = @_;
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');
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
438 $e->rollback if $ret == 0;
439 $e->commit if $ret > 0;
444 __PACKAGE__->register_method(
445 api_name => 'open-ils.reporter.report.delete.cascade',
446 method => 'cascade_delete_report');
448 #__PACKAGE__->register_method(
449 # api_name => 'open-ils.reporter.report.delete.cascade.force',
450 # method => 'cascade_delete_report');
452 sub cascade_delete_report {
453 my( $self, $conn, $auth, $reportId ) = @_;
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');
459 my $ret = cascade_delete_report_impl($e, $e->requestor->id, $reportId);
460 return $ret if ref $ret; # some fatal event occurred
462 $e->rollback if $ret == 0;
463 $e->commit if $ret > 0;
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 ) = @_;
476 # fetch the template to delete
477 my $template = $e->search_reporter_template(
478 {id=>$templateId, owner=>$owner})->[0] or return 0;
480 # fetch he attached report IDs for this owner
481 my $reports = $e->search_reporter_report(
482 {template=>$templateId, owner=>$owner},{idlist=>1});
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;
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});
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;
502 $e->delete_reporter_template($template)
503 or return $e->die_event;
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 ) = @_;
515 # fetch the report to delete
516 my $report = $e->search_reporter_report(
517 {id=>$reportId, owner=>$owner})->[0] or return 0;
519 # fetch the attached schedule IDs for this owner
520 my $scheds = $e->search_reporter_schedule(
521 {report=>$reportId, runner=>$owner},{idlist=>1});
523 # delete the attached schedules
524 for my $sched (@$scheds) {
525 my $evt = delete_schedule_impl($e, $sched);
529 # fetch all schedules attached to this report that
530 # do not belong to $owner. If there are any, we can't
532 my $alt_scheds = $e->search_reporter_schedule(
533 {report=>$reportId, runner=>{"!=" => $owner}},{idlist=>1});
535 return 1 if @$alt_scheds;
537 $e->delete_reporter_report($report)
538 or return $e->die_event;
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;
557 __PACKAGE__->register_method(
558 api_name => 'open-ils.reporter.report.delete',
559 method => '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');
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;
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');
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;
593 __PACKAGE__->register_method(
594 api_name => 'open-ils.reporter.template_has_reports',
595 method => '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});
606 __PACKAGE__->register_method(
607 api_name => 'open-ils.reporter.report_has_output',
608 method => '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});
621 __PACKAGE__->register_method(
622 method => 'org_full_path',
623 api_name => 'open-ils.reporter.org_unit.full_path');
626 my( $self, $conn, $orgid ) = @_;
627 return $U->storagereq(
628 'open-ils.storage.actor.org_unit.full_path.atomic', $orgid );
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');
643 my $hint = $$args{hint};
644 my $org_col = $$args{org_column};
645 my $orgs = $$args{org};
647 # if ($orgs && !$$args{no_fetch}) {
649 # ->method_lookup( 'open-ils.reporter.org_unit.full_path' )
651 # $orgs = [ map {$_->id} @$orgs ];
654 # Find the class the iplements the given hint
656 $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
658 return undef unless $class->Selector;
660 $class =~ s/Fieldmapper:://og;
667 $method = "search_$class";
668 $margs = { $org_col => $orgs };
670 $method = "retrieve_all_$class";
673 $logger->info("reporter.magic_fetch => $method");
675 return $e->$method($margs);
678 __PACKAGE__->register_method(
679 method => 'search_templates',
680 api_name => 'open-ils.reporter.search.templates',
684 sub search_templates {
685 my ($self, $client, $auth, $query_args) = @_;
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};
693 return undef unless $query_string;
695 my $e = new_rstore_editor(authtoken => $auth);
696 return $e->event unless $e->checkauth;
698 my ($visible_folders) = $self
699 ->method_lookup('open-ils.reporter.folder.visible.retrieve')
700 ->run($auth, 'template');
702 my @visible_folder_ids = map { $_->id } @$visible_folders;
704 return undef unless @$visible_folders;
707 select => {rt => ['id']},
710 folder => \@visible_folder_ids,
717 if ($folder) { # search request for specific folder + sub-folders
718 my ($root_folder) = grep { $_->id == $folder} @$visible_folders;
720 return OpenILS::Event->new('BAD_PARAMS',
721 desc => q/Cannot search requested folder/) unless $root_folder;
723 # find all folders that are descendants of the selected folder.
729 push(@ffilter, $node->id);
730 my @children = grep { $_->parent == $node->id } @$visible_folders;
731 $finder->($_) for @children;
734 $finder->($root_folder);
735 $query->{where}->{folder} = \@ffilter;
738 $query_string =~ s/^\s+|\s+$//gm; # remove open/trailing spaces
739 my @query_parts = split(/ +/, $query_string);
741 # Compile the query parts and searched fields down to a JSON-query
742 # structure like this. Note that single-field searches have no
747 # {$field1 => {~* => $value1}},
748 # {$field2 => {~* => $value1}}
751 # {$field1 => {~* => $value2}},
752 # {$field2 => {~* => $value2}}
756 for my $part (@query_parts) {
760 $subq = {'-or' => []};
761 for my $field (@$fields) {
762 push(@{$subq->{'-or'}}, {$field => {'~*' => "(^| )$part"}});
765 $subq = {$fields->[0] => {'~*' => "(^| )$part"}};
768 push(@{$query->{where}->{'-and'}}, $subq);
771 my $template_ids = $e->json_query($query);
773 # Flesh template owner for consistency with retrieve_folder_data
774 my $flesh = {flesh => 1, flesh_fields => {rt => ['owner']}};
776 $client->respond($e->retrieve_reporter_template([$_->{id}, $flesh]))