authorizeAbility($request, 'reports.finance.view'); $organization = $this->organization($request); $member = $this->member($request); $branchScope = app(OrganizationResolver::class)->branchScope($member); $branches = Branch::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->where('is_active', true) ->orderBy('name') ->get(); return view('care.admin.analytics', [ 'title' => 'Reports', 'badge' => 'Analytics · Facility performance', 'description' => 'Live operational and financial snapshot. Open a detailed report for date ranges and CSV export.', 'adminOverview' => $this->adminOverview->reportsHub( $organization, $this->ownerRef($request), $branchScope, ), 'catalog' => config('care.report_types'), 'branches' => $branches, ]); } public function show(Request $request, string $type): View { $this->authorizeReport($request, $type); if ($type === 'assessments') { if ($upgrade = $this->proFeatureUpgradeView( $request, 'assessment_analytics', 'Assessment analytics', 'Org-level clinical form and outcome analytics across patients.', 'enterprise', 'GHS '.number_format(app(PlanService::class)->enterprisePricePerBranchMinor() / 100, 0) .' / branch / month for Enterprise — assessment analytics, priority support, and advanced workflows on top of Pro.', )) { return $upgrade; } } $organization = $this->organization($request); $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); $branchId = $request->input('branch_id') ? (int) $request->input('branch_id') : $branchScope; [$from, $to] = $this->dateRange($request); $data = $this->reportData($request, $type, $organization->id, $from, $to, $branchId); $branches = Branch::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->orderBy('name') ->get(); return view('care.reports.show', [ 'type' => $type, 'label' => config('care.report_types')[$type] ?? $type, 'data' => $data, 'from' => $from->toDateString(), 'to' => $to->toDateString(), 'branchId' => $branchId, 'branches' => $branches, 'canExport' => app(\App\Services\Care\CarePermissions::class) ->can($this->member($request), 'reports.finance.export'), ]); } public function export(Request $request, string $type): StreamedResponse|RedirectResponse { $this->authorizeReport($request, $type); if ($type === 'assessments') { if ($deny = $this->denyWithoutFeature($request, 'assessment_analytics', self::ASSESSMENT_ANALYTICS_UPSELL)) { return $deny; } } abort_unless( app(\App\Services\Care\CarePermissions::class)->can($this->member($request), 'reports.finance.export'), 403, ); $organization = $this->organization($request); $branchScope = app(OrganizationResolver::class)->branchScope($this->member($request)); $branchId = $request->input('branch_id') ? (int) $request->input('branch_id') : $branchScope; [$from, $to] = $this->dateRange($request); $data = $this->reportData($request, $type, $organization->id, $from, $to, $branchId); $filename = "care-report-{$type}-".now()->format('Y-m-d').'.csv'; return response()->streamDownload(function () use ($data, $type) { $handle = fopen('php://output', 'w'); if ($type === 'clinical' && isset($data['diagnoses'])) { fputcsv($handle, ['Diagnosis', 'Count']); foreach ($data['diagnoses'] as $row) { fputcsv($handle, [$row->description, $row->total]); } } elseif ($type === 'assessments') { fputcsv($handle, ['Section', 'Key', 'Value']); foreach ($data['summary'] ?? [] as $key => $value) { fputcsv($handle, ['summary', $key, $value]); } foreach ($data['by_template'] ?? [] as $row) { fputcsv($handle, ['template', $row->template_code, "{$row->total} total / {$row->completed} completed"]); } foreach ($data['by_pathway'] ?? [] as $row) { fputcsv($handle, ['pathway', $row->pathway_code, "{$row->total} activations"]); } foreach ($data['score_averages'] ?? [] as $row) { fputcsv($handle, ['score_avg', $row->template_code, round((float) $row->avg_total, 2)]); } } else { fputcsv($handle, ['Metric', 'Value']); foreach ($data as $key => $value) { fputcsv($handle, [$key, is_scalar($value) ? $value : json_encode($value)]); } } fclose($handle); }, $filename, ['Content-Type' => 'text/csv']); } /** * @return array */ protected function reportData(Request $request, string $type, int $organizationId, Carbon $from, Carbon $to, ?int $branchId): array { return match ($type) { 'patients' => $this->reports->patientsReport($this->ownerRef($request), $organizationId, $from, $to, $branchId), 'appointments' => $this->reports->appointmentsReport($this->ownerRef($request), $organizationId, $from, $to, $branchId), 'laboratory' => $this->reports->laboratoryReport($this->ownerRef($request), $organizationId, $from, $to, $branchId), 'finance' => $this->reports->financeReport($this->ownerRef($request), $organizationId, $from, $to, $branchId), 'clinical' => ['diagnoses' => $this->reports->clinicalReport($this->ownerRef($request), $organizationId, $from, $to)], 'assessments' => $this->reports->assessmentsReport($this->ownerRef($request), $organizationId, $from, $to, $branchId), default => abort(404), }; } protected function authorizeReport(Request $request, string $type): void { abort_unless(array_key_exists($type, config('care.report_types')), 404); $this->authorizeAbility($request, 'reports.finance.view'); if ($type === 'assessments') { $organization = $this->organization($request); abort_unless( app(CareFeatures::class)->enabled($organization, CareFeatures::ASSESSMENTS_ENGINE), 404, ); // Plan gate: show() renders upgrade UI; export() redirects via denyWithoutFeature. } } /** * @return array{0: Carbon, 1: Carbon} */ protected function dateRange(Request $request): array { $from = Carbon::parse($request->input('from', now()->subDays(30)->toDateString()))->startOfDay(); $to = Carbon::parse($request->input('to', now()->toDateString()))->endOfDay(); return [$from, $to]; } }