]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/perlmods/lib/OpenILS/Application/Reporter.pm
Revert "LP#1635737 Use new OpenSRF interval_to_seconds() context"
[working/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 ) = @_;
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     return $e->$meth([{ folder => $folderid }, $flesh]);
130 }
131
132 __PACKAGE__->register_method(
133     api_name => 'open-ils.reporter.schedule.retrieve_by_folder',
134     method => 'retrieve_schedules');
135 sub retrieve_schedules {
136     my( $self, $conn, $auth, $folderId, $limit, $complete ) = @_;
137     my $e = new_rstore_editor(authtoken=>$auth);
138     return $e->event unless $e->checkauth;
139     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
140
141     my $search = { folder => $folderId };
142     my $query = [
143         { folder => $folderId },
144         {
145             order_by => { rs => 'run_time DESC' } ,
146             flesh => 1,
147             flesh_fields => { rs => ['report'] }
148         }
149     ];
150
151     $query->[1]->{limit} = $limit if $limit;
152     $query->[0]->{complete_time} = undef unless $complete;
153     $query->[0]->{complete_time} = { '!=' => undef } if $complete;
154
155     return $e->search_reporter_schedule($query);
156 }
157
158 __PACKAGE__->register_method(
159     api_name => 'open-ils.reporter.schedule.retrieve',
160     method => 'retrieve_schedules');
161 sub retrieve_schedule {
162     my( $self, $conn, $auth, $sched_id ) = @_;
163     my $e = new_rstore_editor(authtoken=>$auth);
164     return $e->event unless $e->checkauth;
165     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
166     my $s = $e->retrieve_reporter_schedule($sched_id)
167         or return $e->event;
168     return $s;
169 }
170
171
172 __PACKAGE__->register_method(
173     api_name => 'open-ils.reporter.template.create',
174     method => 'create_template');
175 sub create_template {
176     my( $self, $conn, $auth, $template ) = @_;
177     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
178     return $e->die_event unless $e->checkauth;
179     return $e->die_event unless $e->allowed('RUN_REPORTS');
180     return $e->die_event unless $e->allowed('CREATE_REPORT_TEMPLATE');
181     $template->owner($e->requestor->id);
182
183     my $existing = $e->search_reporter_template( {owner=>$template->owner,
184             folder=>$template->folder, name=>$template->name},{idlist=>1});
185     return OpenILS::Event->new('REPORT_TEMPLATE_EXISTS') if @$existing;
186
187     my $tmpl = $e->create_reporter_template($template)
188         or return $e->die_event;
189     $e->commit;
190     return $tmpl;
191 }
192
193
194
195
196
197 __PACKAGE__->register_method(
198     api_name => 'open-ils.reporter.report.create',
199     method => 'create_report');
200 sub create_report {
201     my( $self, $conn, $auth, $report, $schedule ) = @_;
202     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
203     return $e->die_event unless $e->checkauth;
204     return $e->die_event unless $e->allowed('RUN_REPORTS');
205     $report->owner($e->requestor->id);
206
207     my $existing = $e->search_reporter_report( {owner=>$report->owner,
208             folder=>$report->folder, name=>$report->name},{idlist=>1});
209     return OpenILS::Event->new('REPORT_REPORT_EXISTS') if @$existing;
210
211     my $rpt = $e->create_reporter_report($report)
212         or return $e->die_event;
213     $schedule->report($rpt->id);
214     $schedule->runner($e->requestor->id);
215     $e->create_reporter_schedule($schedule) or return $e->die_event;
216     $e->commit;
217     return $rpt;
218 }
219
220
221 __PACKAGE__->register_method(
222     api_name => 'open-ils.reporter.schedule.create',
223     method => 'create_schedule');
224 sub create_schedule {
225     my( $self, $conn, $auth, $schedule ) = @_;
226     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
227     return $e->die_event unless $e->checkauth;
228     return $e->die_event unless $e->allowed('RUN_REPORTS');
229     my $sched = $e->create_reporter_schedule($schedule)
230         or return $e->die_event;
231     $e->commit;
232     return $sched;
233 }
234
235 __PACKAGE__->register_method(
236     api_name => 'open-ils.reporter.template.retrieve',
237     method => 'retrieve_template');
238 sub retrieve_template {
239     my( $self, $conn, $auth, $id ) = @_;
240     my $e = new_rstore_editor(authtoken=>$auth);
241     return $e->event unless $e->checkauth;
242     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
243     my $t = $e->retrieve_reporter_template($id)
244         or return $e->event;
245     return $t;
246 }
247
248
249 __PACKAGE__->register_method(
250     api_name => 'open-ils.reporter.report.retrieve',
251     method => 'retrieve_report');
252 sub retrieve_report {
253     my( $self, $conn, $auth, $id ) = @_;
254     my $e = new_rstore_editor(authtoken=>$auth);
255     return $e->event unless $e->checkauth;
256     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
257     my $r = $e->retrieve_reporter_report($id)
258         or return $e->event;
259     return $r;
260 }
261
262 __PACKAGE__->register_method(
263     api_name => 'open-ils.reporter.report.fleshed.retrieve',
264     method => 'retrieve_fleshed_report',
265     signature => {
266         desc => q/Returns report, fleshed with template, template.owner
267         and schedules. Fleshes report.runs() as a single-item array 
268         containing the most recently created reporter.schedule./
269     }
270 );
271 sub retrieve_fleshed_report {
272     my( $self, $conn, $auth, $id, $options ) = @_;
273     $options ||= {};
274
275     my $e = new_rstore_editor(authtoken=>$auth);
276     return $e->event unless $e->checkauth;
277     return $e->event unless $e->allowed(['RUN_REPORTS','VIEW_REPORT_OUTPUT']);
278     my $r = $e->retrieve_reporter_report([
279         $id, {
280         flesh => 2,
281         flesh_fields => {
282             rr => ['template'],
283             rt => ['owner']
284         }
285     }]) or return $e->event;
286
287     my $output = $e->search_reporter_schedule([
288         {report => $id},
289         {limit => 1, order_by => {rs => 'run_time DESC'}}
290     ]);
291
292     $r->runs($output);
293
294     return $r;
295 }
296
297
298
299
300 __PACKAGE__->register_method(
301     api_name => 'open-ils.reporter.template.update',
302     method => 'update_template');
303 sub update_template {
304     my( $self, $conn, $auth, $tmpl ) = @_;
305     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
306     return $e->die_event unless $e->checkauth;
307     return $e->die_event unless $e->allowed('RUN_REPORTS');
308     return $e->die_event unless $e->allowed('CREATE_REPORT_TEMPLATE');
309     my $t = $e->retrieve_reporter_template($tmpl->id)
310         or return $e->die_event;
311     return 0 if $t->owner ne $e->requestor->id;
312     $e->update_reporter_template($tmpl)
313         or return $e->die_event;
314     $e->commit;
315     return 1;
316 }
317
318
319 __PACKAGE__->register_method(
320     api_name => 'open-ils.reporter.report.update',
321     method => 'update_report');
322 sub update_report {
323     my( $self, $conn, $auth, $report ) = @_;
324     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
325     return $e->die_event unless $e->checkauth;
326     return $e->die_event unless $e->allowed('RUN_REPORTS');
327     my $r = $e->retrieve_reporter_report($report->id)
328         or return $e->die_event;
329     if( $r->owner ne $e->requestor->id ) {
330         $e->rollback;
331         return 0;
332     }
333     $e->update_reporter_report($report)
334         or return $e->die_event;
335     $e->commit;
336     return 1;
337 }
338
339
340 __PACKAGE__->register_method(
341     api_name => 'open-ils.reporter.schedule.update',
342     method => 'update_schedule');
343 sub update_schedule {
344     my( $self, $conn, $auth, $schedule ) = @_;
345     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
346     return $e->die_event unless $e->checkauth;
347     return $e->die_event unless $e->allowed('RUN_REPORTS');
348     my $s = $e->retrieve_reporter_schedule($schedule->id)
349         or return $e->die_event;
350     my $r = $e->retrieve_reporter_report($s->report)
351         or return $e->die_event;
352     if( $r->owner ne $e->requestor->id ) {
353         $e->rollback;
354         return 0;
355     }
356     $e->update_reporter_schedule($schedule)
357         or return $e->die_event;
358     $e->commit;
359     return 1;
360 }
361
362
363 __PACKAGE__->register_method(
364     api_name => 'open-ils.reporter.folder.update',
365     method => 'update_folder');
366 sub update_folder {
367     my( $self, $conn, $auth, $type, $folder ) = @_;
368     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
369     return $e->die_event unless $e->checkauth;
370     return $e->die_event unless $e->allowed('RUN_REPORTS');
371     my $meth = "retrieve_reporter_${type}_folder";
372     my $f = $e->$meth($folder->id) or return $e->die_event;
373     return 0 if $f->owner ne $e->requestor->id;
374     $meth = "update_reporter_${type}_folder";
375     $e->$meth($folder) or return $e->die_event;
376     $e->commit;
377     return 1;
378 }
379
380
381 __PACKAGE__->register_method(
382     api_name => 'open-ils.reporter.folder.delete',
383     method => 'delete_folder');
384 sub delete_folder {
385     my( $self, $conn, $auth, $type, $folderId ) = @_;
386     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
387     return $e->die_event unless $e->checkauth;
388     return $e->die_event unless $e->allowed('RUN_REPORTS');
389     my $meth = "retrieve_reporter_${type}_folder";
390     my $f = $e->$meth($folderId) or return $e->die_event;
391     return 0 if $f->owner ne $e->requestor->id;
392     $meth = "delete_reporter_${type}_folder";
393     $e->$meth($f) or return $e->die_event;
394     $e->commit;
395     return 1;
396 }
397
398
399 __PACKAGE__->register_method(
400     api_name => 'open-ils.reporter.template.delete',
401     method => 'delete_template');
402 sub delete_template {
403     my( $self, $conn, $auth, $templateId ) = @_;
404     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
405     return $e->die_event unless $e->checkauth;
406     return $e->die_event unless $e->allowed('RUN_REPORTS');
407
408     my $t = $e->retrieve_reporter_template($templateId)
409         or return $e->die_event;
410     return 0 if $t->owner ne $e->requestor->id;
411     $e->delete_reporter_template($t) or return $e->die_event;
412     $e->commit;
413     return 1;
414 }
415
416
417
418 __PACKAGE__->register_method(
419     api_name => 'open-ils.reporter.template.delete.cascade',
420     method => 'cascade_delete_template');
421
422 #__PACKAGE__->register_method(
423 #   api_name => 'open-ils.reporter.template.delete.cascade.force',
424 #   method => 'cascade_delete_template');
425
426 sub cascade_delete_template {
427     my( $self, $conn, $auth, $templateId ) = @_;
428
429     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
430     return $e->die_event unless $e->checkauth;
431     return $e->die_event unless $e->allowed('RUN_REPORTS');
432
433     my $ret = cascade_delete_template_impl(
434         $e, $e->requestor->id, $templateId, ($self->api_name =~ /force/o) );
435     return $ret if ref $ret; # some fatal event occurred
436
437     $e->rollback if $ret == 0;
438     $e->commit if $ret > 0;
439     return $ret;
440 }
441
442
443 __PACKAGE__->register_method(
444     api_name => 'open-ils.reporter.report.delete.cascade',
445     method => 'cascade_delete_report');
446
447 #__PACKAGE__->register_method(
448 #   api_name => 'open-ils.reporter.report.delete.cascade.force',
449 #   method => 'cascade_delete_report');
450
451 sub cascade_delete_report {
452     my( $self, $conn, $auth, $reportId ) = @_;
453
454     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
455     return $e->die_event unless $e->checkauth;
456     return $e->die_event unless $e->allowed('RUN_REPORTS');
457
458     my $ret = cascade_delete_report_impl($e, $e->requestor->id, $reportId);
459     return $ret if ref $ret; # some fatal event occurred
460
461     $e->rollback if $ret == 0;
462     $e->commit if $ret > 0;
463     return $ret;
464 }
465
466
467 # performs a cascading template delete
468 # returns 2 if all data was deleted
469 # returns 1 if some data was deleted
470 # returns 0 if no data was deleted
471 # returns event on error
472 sub cascade_delete_template_impl {
473     my( $e, $owner, $templateId ) = @_;
474
475     # fetch the template to delete
476     my $template = $e->search_reporter_template(
477         {id=>$templateId, owner=>$owner})->[0] or return 0;
478
479     # fetch he attached report IDs for this  owner
480     my $reports = $e->search_reporter_report(
481         {template=>$templateId, owner=>$owner},{idlist=>1});
482
483     # delete the attached reports
484     my $all_rpts_deleted = 1;
485     for my $r (@$reports) {
486         my $evt = cascade_delete_report_impl($e, $owner, $r);
487         return $evt if ref $evt;
488         $all_rpts_deleted = 0 unless $evt == 2;
489     }
490
491     # fetch all reports attached to this template that
492     # do not belong to $owner.  If there are any, we can't
493     # delete the template
494     my $alt_reports = $e->search_reporter_report(
495         {template=>$templateId, owner=>{"!=" => $owner}},{idlist=>1});
496
497     # all_rpts_deleted will be false if a report has an
498     # attached scheduled owned by a different user
499     return 1 if @$alt_reports or not $all_rpts_deleted;
500
501     $e->delete_reporter_template($template)
502         or return $e->die_event;
503     return 2;
504 }
505
506 # performs a cascading report delete
507 # returns 2 if all data was deleted
508 # returns 1 if some data was deleted
509 # returns 0 if no data was deleted
510 # returns event on error
511 sub cascade_delete_report_impl {
512     my( $e, $owner, $reportId ) = @_;
513
514     # fetch the report to delete
515     my $report = $e->search_reporter_report(
516         {id=>$reportId, owner=>$owner})->[0] or return 0;
517
518     # fetch the attached schedule IDs for this owner
519     my $scheds = $e->search_reporter_schedule(
520         {report=>$reportId, runner=>$owner},{idlist=>1});
521
522     # delete the attached schedules
523     for my $sched (@$scheds) {
524         my $evt = delete_schedule_impl($e, $sched);
525         return $evt if $evt;
526     }
527
528     # fetch all schedules attached to this report that
529     # do not belong to $owner.  If there are any, we can't
530     # delete the report
531     my $alt_scheds = $e->search_reporter_schedule(
532         {report=>$reportId, runner=>{"!=" => $owner}},{idlist=>1});
533
534     return 1 if @$alt_scheds;
535
536     $e->delete_reporter_report($report)
537         or return $e->die_event;
538
539     return 2;
540 }
541
542
543 # deletes the requested schedule
544 # returns undef on success, event on error
545 sub delete_schedule_impl {
546     my( $e, $schedId ) = @_;
547     my $s = $e->retrieve_reporter_schedule($schedId)
548         or return $e->die_event;
549     $e->delete_reporter_schedule($s) or return $e->die_event;
550     return undef;
551 }
552
553
554
555
556 __PACKAGE__->register_method(
557     api_name => 'open-ils.reporter.report.delete',
558     method => 'delete_report');
559 sub delete_report {
560     my( $self, $conn, $auth, $reportId ) = @_;
561     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
562     return $e->die_event unless $e->checkauth;
563     return $e->die_event unless $e->allowed('RUN_REPORTS');
564
565     my $t = $e->retrieve_reporter_report($reportId)
566         or return $e->die_event;
567     return 0 if $t->owner ne $e->requestor->id;
568     $e->delete_reporter_report($t) or return $e->die_event;
569     $e->commit;
570     return 1;
571 }
572
573
574 __PACKAGE__->register_method(
575     api_name => 'open-ils.reporter.schedule.delete',
576     method => 'delete_schedule');
577 sub delete_schedule {
578     my( $self, $conn, $auth, $scheduleId ) = @_;
579     my $e = new_rstore_editor(authtoken=>$auth, xact=>1);
580     return $e->die_event unless $e->checkauth;
581     return $e->die_event unless $e->allowed('RUN_REPORTS');
582
583     my $t = $e->retrieve_reporter_schedule($scheduleId)
584         or return $e->die_event;
585     return 0 if $t->runner ne $e->requestor->id;
586     $e->delete_reporter_schedule($t) or return $e->die_event;
587     $e->commit;
588     return 1;
589 }
590
591
592 __PACKAGE__->register_method(
593     api_name => 'open-ils.reporter.template_has_reports',
594     method => 'has_reports');
595 sub has_reports {
596     my( $self, $conn, $auth, $templateId ) = @_;
597     my $e = new_rstore_editor(authtoken=>$auth);
598     return $e->die_event unless $e->checkauth;
599     return $e->die_event unless $e->allowed('RUN_REPORTS');
600     my $rpts = $e->search_reporter_report({template=>$templateId},{idlist=>1});
601     return 1 if @$rpts;
602     return 0;
603 }
604
605 __PACKAGE__->register_method(
606     api_name => 'open-ils.reporter.report_has_output',
607     method => 'has_output');
608 sub has_output {
609     my( $self, $conn, $auth, $reportId ) = @_;
610     my $e = new_rstore_editor(authtoken=>$auth);
611     return $e->die_event unless $e->checkauth;
612     return $e->die_event unless $e->allowed('RUN_REPORTS');
613     my $outs = $e->search_reporter_schedule({report=>$reportId},{idlist=>1});
614     return 1 if @$outs;
615     return 0;
616 }
617
618
619
620 __PACKAGE__->register_method(
621     method => 'org_full_path',
622     api_name => 'open-ils.reporter.org_unit.full_path');
623
624 sub org_full_path {
625     my( $self, $conn, $orgid ) = @_;
626     return $U->storagereq(
627         'open-ils.storage.actor.org_unit.full_path.atomic', $orgid );
628 }
629
630
631
632
633 __PACKAGE__->register_method(
634     method => 'magic_fetch_all',
635     api_name => 'open-ils.reporter.magic_fetch');
636 sub magic_fetch_all {
637     my( $self, $conn, $auth, $args ) = @_;
638     my $e = new_editor(authtoken => $auth);
639     return $e->event unless $e->checkauth;
640     return $e->event unless $e->allowed('RUN_REPORTS');
641
642     my $hint = $$args{hint};
643     my $org_col = $$args{org_column};
644     my $orgs = $$args{org};
645
646 #   if ($orgs && !$$args{no_fetch}) {
647 #       ($orgs) = $self
648 #               ->method_lookup( 'open-ils.reporter.org_unit.full_path' )
649 #               ->run( @$orgs );
650 #       $orgs = [ map {$_->id} @$orgs ];
651 #   }
652
653     # Find the class the iplements the given hint
654     my ($class) = grep {
655         $Fieldmapper::fieldmap->{$_}{hint} eq $hint } Fieldmapper->classes;
656
657     return undef unless $class->Selector;
658
659     $class =~ s/Fieldmapper:://og;
660     $class =~ s/::/_/og;
661
662     my $method;
663     my $margs;
664
665     if( $org_col ) {
666         $method = "search_$class";
667         $margs = { $org_col => $orgs };
668     } else {
669         $method = "retrieve_all_$class";
670     }
671
672     $logger->info("reporter.magic_fetch => $method");
673
674     return $e->$method($margs);
675 }
676
677
678 1;