*/ public function pageData(MedicalReport $medicalReport): array { $structuredSummary = $this->structuredSummary($medicalReport); $analysisSheet = $this->analysisSheet($medicalReport); $analysisIssues = $this->analysisIssues($medicalReport); if ($structuredSummary !== null || $analysisSheet !== null) { return [ 'report' => [ 'id' => $medicalReport->id, 'name' => $medicalReport->name, 'year' => $medicalReport->year, 'updated_at' => $medicalReport->updated_at?->toIso8601String(), ], 'summarySheet' => $structuredSummary, 'analysisSheet' => $analysisSheet, 'analysisIssues' => $analysisIssues, ]; } $sheetKey = $this->templateWorkbook->firstSheetKey(); $sheet = $sheetKey === null ? null : $this->templateWorkbook->sheet($sheetKey); $departments = $this->templateWorkbook->departments(); $columns = $sheetKey === null ? [] : $this->columns($sheetKey); $rows = $sheetKey === null ? [] : array_values(array_filter(array_map( fn (array $department): ?array => $this->departmentRow($medicalReport, $sheetKey, $department, $columns), $departments, ))); return [ 'report' => [ 'id' => $medicalReport->id, 'name' => $medicalReport->name, 'year' => $medicalReport->year, 'updated_at' => $medicalReport->updated_at?->toIso8601String(), ], 'summarySheet' => $sheet === null ? null : [ 'key' => $sheetKey, 'name' => $sheet['name'], 'columns' => $columns, 'rows' => $rows, 'filled_departments' => collect($rows) ->filter(fn (array $row): bool => $row['filled_count'] > 0) ->count(), 'filled_cells' => collect($rows)->sum('filled_count'), ], 'analysisSheet' => null, 'analysisIssues' => $analysisIssues, ]; } /** * @return array{unmapped_departments: list>, unmapped_metrics: list>, total_count: int} */ private function analysisIssues(MedicalReport $medicalReport): array { $unmappedDepartments = []; $unmappedMetrics = []; foreach ($this->templateWorkbook->departments() as $department) { $template = $this->structuredTemplateRegistry->templateForDepartment($department['key']); if ($template === null) { continue; } foreach (($template['sections'] ?? []) as $section) { $entries = Arr::get( $medicalReport->input_overrides ?? [], 'structured_departments.'.$department['key'].'.'.$template['key'].'.sections.'.$section['key'].'.entries', [], ); if ($entries === []) { continue; } foreach (($section['export_metrics'] ?? []) as $metric) { $analysisColumn = trim((string) ($metric['analysis_column'] ?? '')); if ($analysisColumn === '') { $unmappedMetrics[] = [ 'department' => (string) $department['name'], 'section' => (string) ($section['title'] ?? ''), 'metric' => (string) ($metric['label'] ?? $metric['key'] ?? ''), 'reason' => 'Не указана колонка анализа', ]; } } foreach ($entries as $entry) { $departmentId = trim((string) ($entry['department_id'] ?? '')); if ($departmentId === '') { continue; } if ($this->hospitalUnitSlugForEntry($entry) !== null) { continue; } $sourceDepartment = Department::query() ->whereKey((int) $departmentId) ->value('name'); $unmappedDepartments[] = [ 'department' => (string) $department['name'], 'section' => (string) ($section['title'] ?? ''), 'source_department' => (string) ($sourceDepartment ?? $departmentId), 'reason' => 'Нет связи с подразделением финансистов', ]; } } } $unmappedDepartments = collect($unmappedDepartments) ->unique(fn (array $issue): string => implode('|', $issue)) ->values() ->all(); $unmappedMetrics = collect($unmappedMetrics) ->unique(fn (array $issue): string => implode('|', $issue)) ->values() ->all(); return [ 'unmapped_departments' => $unmappedDepartments, 'unmapped_metrics' => $unmappedMetrics, 'total_count' => count($unmappedDepartments) + count($unmappedMetrics), ]; } /** * @return array|null */ private function analysisSheet(MedicalReport $medicalReport): ?array { $columns = collect(config('medical-report.economist_analysis_columns', [])) ->filter(fn (mixed $column): bool => is_array($column)) ->map(fn (array $column): array => [ 'key' => (string) ($column['key'] ?? ''), 'name' => (string) ($column['label'] ?? ''), 'coordinate' => (string) ($column['coordinate'] ?? ''), ]) ->filter(fn (array $column): bool => $column['key'] !== '') ->values(); if ($columns->isEmpty()) { return null; } $units = HospitalUnit::query() ->with('profile:id,name,sort_order') ->where('is_active', true) ->get() ->sortBy([ fn (HospitalUnit $unit): int => $unit->profile?->sort_order ?? PHP_INT_MAX, fn (HospitalUnit $unit): string => $unit->name, ]) ->values(); $valuesByUnit = []; foreach ($this->templateWorkbook->departments() as $department) { $template = $this->structuredTemplateRegistry->templateForDepartment($department['key']); if ($template === null) { continue; } foreach ($template['sections'] ?? [] as $section) { $entries = Arr::get( $medicalReport->input_overrides ?? [], 'structured_departments.'.$department['key'].'.'.$template['key'].'.sections.'.$section['key'].'.entries', [], ); if ($entries === []) { continue; } foreach (($section['export_metrics'] ?? []) as $metric) { $analysisColumn = (string) ($metric['analysis_column'] ?? ''); if ($analysisColumn === '') { continue; } $rowMode = (string) ($metric['row_mode'] ?? ''); if ($rowMode === 'entry_department') { foreach ($entries as $entry) { $unitSlug = $this->hospitalUnitSlugForEntry($entry); if ($unitSlug === null) { continue; } $value = $this->numericValue($entry[$metric['source_field']] ?? null); $valuesByUnit[$unitSlug][$analysisColumn] = ($valuesByUnit[$unitSlug][$analysisColumn] ?? 0) + $value; } continue; } if ($rowMode === 'fixed_unit') { $unitSlug = (string) ($metric['target_unit_slug'] ?? ''); if ($unitSlug === '') { continue; } $sum = collect($entries)->sum(fn (array $entry): float => $this->numericValue($entry[$metric['source_field']] ?? null)); $valuesByUnit[$unitSlug][$analysisColumn] = ($valuesByUnit[$unitSlug][$analysisColumn] ?? 0) + $sum; } } } } if ($valuesByUnit === []) { return null; } $rows = $units->map(function (HospitalUnit $unit) use ($columns, $valuesByUnit): array { $unitValues = $valuesByUnit[$unit->slug] ?? []; return [ 'profile_name' => $unit->profile?->name, 'unit_name' => $unit->name, 'filled_count' => collect($unitValues) ->filter(fn (float|int $value): bool => (float) $value !== 0.0) ->count(), 'values' => $columns->map(fn (array $column): array => [ 'key' => $column['key'], 'value' => $this->formatNumber((float) ($unitValues[$column['key']] ?? 0)), ])->all(), ]; })->all(); return [ 'key' => 'economist-analysis', 'name' => 'Матрица экономистов', 'columns' => $columns->all(), 'rows' => $rows, 'filled_units' => collect($rows)->filter(fn (array $row): bool => $row['filled_count'] > 0)->count(), 'filled_cells' => collect($rows)->sum('filled_count'), ]; } /** * @return array|null */ private function structuredSummary(MedicalReport $medicalReport): ?array { $rows = []; foreach ($this->templateWorkbook->departments() as $department) { $template = $this->structuredTemplateRegistry->templateForDepartment($department['key']); if ($template === null) { continue; } if (isset($template['sections']) && is_array($template['sections'])) { foreach ($template['sections'] as $section) { $entries = Arr::get( $medicalReport->input_overrides ?? [], 'structured_departments.'.$department['key'].'.'.$template['key'].'.sections.'.$section['key'].'.entries', [], ); if ($entries === []) { continue; } $totals = $this->exportTotals( $entries, $section['fields'] ?? [], $section['export_metrics'] ?? [], ); if ($totals === []) { continue; } $rows[] = [ 'department' => (string) ($section['economist_label'] ?? ($department['name'].' / '.$section['title'])), 'filled_count' => count(array_filter($totals, fn (array $value): bool => $value['value'] !== '0')), 'values' => $totals, ]; } continue; } $entries = Arr::get( $medicalReport->input_overrides ?? [], 'structured_departments.'.$department['key'].'.'.$template['key'].'.entries', [], ); if ($entries === []) { continue; } $totals = $this->exportTotals( $entries, $template['fields'] ?? [], $template['export_metrics'] ?? [], ); if ($totals === []) { continue; } $rows[] = [ 'department' => $department['name'], 'filled_count' => count(array_filter($totals, fn (array $value): bool => $value['value'] !== '0')), 'values' => $totals, ]; } if ($rows === []) { return null; } $columns = collect($rows) ->flatMap(fn (array $row): array => $row['values']) ->unique('key') ->values() ->map(fn (array $value): array => [ 'name' => $value['name'], 'coordinate' => $value['key'], ]) ->all(); return [ 'key' => 'structured-summary', 'name' => 'Итоги отделений', 'columns' => $columns, 'rows' => collect($rows) ->map(function (array $row) use ($columns): array { $indexedValues = collect($row['values'])->keyBy('key'); return [ 'department' => $row['department'], 'filled_count' => $row['filled_count'], 'values' => collect($columns) ->map(fn (array $column): array => [ 'name' => $column['name'], 'value' => (string) ($indexedValues->get($column['coordinate'])['value'] ?? '0'), ]) ->all(), ]; }) ->all(), 'filled_departments' => count($rows), 'filled_cells' => (int) collect($rows)->sum('filled_count'), ]; } /** * @param list> $entries * @param list> $fields * @return list */ private function exportTotals(array $entries, array $fields, array $exportMetrics): array { $resolvedMetrics = $exportMetrics !== [] ? collect($exportMetrics) : collect($fields) ->filter(fn (array $field): bool => ($field['type'] ?? 'text') === 'number') ->map(fn (array $field): array => [ 'key' => (string) $field['key'].'_total', 'label' => (string) $field['label'], 'source_field' => (string) $field['key'], 'aggregation' => 'sum', ]); return $resolvedMetrics ->map(function (array $metric) use ($entries): array { $sum = collect($entries)->sum(function (array $entry) use ($metric): float { $value = trim((string) ($entry[$metric['source_field']] ?? '')); if ($value === '') { return 0; } return (float) str_replace(',', '.', $value); }); return [ 'key' => (string) $metric['key'], 'name' => (string) $metric['label'], 'value' => $this->formatNumber($sum), ]; }) ->values() ->all(); } private function hospitalUnitSlugForEntry(array $entry): ?string { $departmentId = $entry['department_id'] ?? null; if ($departmentId === null || trim((string) $departmentId) === '') { return null; } /** @var Department|null $department */ $department = Department::query() ->find((int) $departmentId); if ($department === null) { return null; } if ($department->hospital_unit_id !== null) { return HospitalUnit::query() ->whereKey($department->hospital_unit_id) ->value('slug'); } return HospitalUnit::query() ->where('name', $department->name) ->value('slug'); } private function numericValue(mixed $value): float { $stringValue = trim((string) $value); if ($stringValue === '') { return 0; } return (float) str_replace(',', '.', $stringValue); } private function formatNumber(float $value): string { if ((float) ((int) $value) === $value) { return (string) ((int) $value); } return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.'); } /** * @return list */ private function columns(string $sheetKey): array { $sheet = $this->templateWorkbook->sheet($sheetKey); return collect($sheet['fields_by_department']) ->flatten(1) ->sortBy('column') ->unique('column') ->values() ->map(fn (array $field): array => [ 'name' => $field['column_label'], 'coordinate' => $field['coordinate'], ]) ->all(); } /** * @param list $columns * @return array|null */ private function departmentRow( MedicalReport $medicalReport, string $sheetKey, array $department, array $columns, ): ?array { $fields = $this->templateWorkbook->sheet($sheetKey)['fields_by_department'][$department['key']] ?? []; if ($fields === []) { return null; } $valuesByColumn = collect($fields) ->keyBy('column') ->map(function (array $field) use ($medicalReport, $sheetKey, $department): string { $persistedValues = $this->reportWorkbook->persistedSheetValues( $medicalReport, $department['key'], $sheetKey, ); return (string) Arr::get($persistedValues, $field['coordinate'], $field['default']); }); return [ 'department' => $department['name'], 'filled_count' => $valuesByColumn ->filter(fn (string $value): bool => trim($value) !== '' && trim($value) !== '0') ->count(), 'values' => collect($columns) ->map(fn (array $column): array => [ 'name' => $column['name'], 'value' => (string) ($valuesByColumn->get(Coordinates::split($column['coordinate'])['column']) ?? ''), ]) ->all(), ]; } }