Files
econom-calculator/app/Support/MedicalReport/ReportWorkbook.php
brusnitsyn 3edc8e667e
Some checks failed
tests / ci (8.5) (push) Has been cancelled
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
first commit
2026-04-03 17:20:05 +09:00

310 lines
11 KiB
PHP

<?php
namespace App\Support\MedicalReport;
use App\Models\MedicalReport;
use Illuminate\Support\Arr;
class ReportWorkbook
{
public function __construct(
private readonly TemplateWorkbook $templateWorkbook,
private readonly StructuredTemplateRegistry $structuredTemplateRegistry,
) {}
/**
* @return array<string, mixed>
*/
public function pageData(
MedicalReport $medicalReport,
?string $selectedDepartmentKey = null,
?string $selectedSheetKey = null,
): array {
$definition = $this->templateWorkbook->definition();
$departments = $definition['departments'];
$selectedDepartmentKey ??= $departments[0]['key'] ?? null;
$structuredTemplate = $selectedDepartmentKey === null
? null
: $this->structuredTemplate($medicalReport, $selectedDepartmentKey);
$availableSheets = $structuredTemplate === null
? array_values(array_filter(
array_map(
fn (string $sheetKey): array => $this->sheetSummary($medicalReport, $sheetKey, $selectedDepartmentKey),
$definition['order'],
),
fn (array $sheet): bool => $sheet['editable_count'] > 0,
))
: [];
$selectedSheetKey = $structuredTemplate === null
? ($selectedSheetKey ?? $availableSheets[0]['key'] ?? null)
: null;
$currentSheet = $structuredTemplate === null
? (collect($availableSheets)->firstWhere('key', $selectedSheetKey) ?? $availableSheets[0] ?? null)
: null;
return [
'report' => [
'id' => $medicalReport->id,
'name' => $medicalReport->name,
'year' => $medicalReport->year,
'updated_at' => $medicalReport->updated_at?->toIso8601String(),
],
'departments' => array_map(
fn (array $department): array => $this->departmentSummary($medicalReport, $department),
$departments,
),
'selectedDepartment' => $selectedDepartmentKey,
'selectedSheet' => $selectedSheetKey,
'sheets' => $availableSheets,
'structuredTemplate' => $structuredTemplate,
'currentSheet' => $currentSheet === null ? null : [
'key' => $currentSheet['key'],
'name' => $currentSheet['name'],
'fields' => $this->sheetFields($medicalReport, $currentSheet['key'], $selectedDepartmentKey),
],
'summary' => [
'department_count' => count($departments),
'sheet_count' => $structuredTemplate === null ? count($availableSheets) : 1,
'filled_count' => $this->filledCount($medicalReport, $selectedDepartmentKey),
'editable_count' => $structuredTemplate === null
? array_sum(array_column($availableSheets, 'editable_count'))
: $this->structuredEditableCount($structuredTemplate),
],
];
}
/**
* @return array<string, string>
*/
public function persistedSheetValues(MedicalReport $medicalReport, string $departmentKey, string $sheetKey): array
{
/** @var array<string, array<string, array<string, string>>> $overrides */
$overrides = $medicalReport->input_overrides ?? [];
return Arr::get($overrides, $departmentKey.'.'.$sheetKey, []);
}
/**
* @return array<string, mixed>
*/
private function departmentSummary(MedicalReport $medicalReport, array $department): array
{
$filledCount = $this->filledCount($medicalReport, $department['key']);
return [
...$department,
'filled_count' => $filledCount,
'has_template' => $filledCount > 0
|| $this->structuredTemplateRegistry->templateForDepartment($department['key']) !== null
|| collect($this->templateWorkbook->sheetKeys())->contains(
fn (string $sheetKey): bool => ! empty(
$this->templateWorkbook->sheet($sheetKey)['fields_by_department'][$department['key']] ?? []
)
),
];
}
/**
* @return array<string, mixed>
*/
private function sheetSummary(MedicalReport $medicalReport, string $sheetKey, ?string $departmentKey): array
{
$sheet = $this->templateWorkbook->sheet($sheetKey);
$fields = $departmentKey !== null
? Arr::get($sheet, 'fields_by_department.'.$departmentKey, [])
: [];
$persistedValues = $departmentKey !== null
? $this->persistedSheetValues($medicalReport, $departmentKey, $sheetKey)
: [];
return [
'key' => $sheetKey,
'name' => $sheet['name'],
'editable_count' => count($fields),
'filled_count' => count($persistedValues),
];
}
/**
* @return list<array<string, mixed>>
*/
private function sheetFields(MedicalReport $medicalReport, string $sheetKey, ?string $departmentKey): array
{
if ($departmentKey === null) {
return [];
}
$sheet = $this->templateWorkbook->sheet($sheetKey);
$persistedValues = $this->persistedSheetValues($medicalReport, $departmentKey, $sheetKey);
return array_values(array_map(
function (array $field) use ($persistedValues): array {
$currentValue = Arr::get($persistedValues, $field['coordinate'], $field['default']);
return [
...$field,
'description' => implode(' / ', array_filter([
$field['row_label'],
$field['column_label'],
$field['coordinate'],
])),
'value' => (string) $currentValue,
];
},
Arr::get($sheet, 'fields_by_department.'.$departmentKey, []),
));
}
private function filledCount(MedicalReport $medicalReport, ?string $departmentKey): int
{
if ($departmentKey === null) {
return 0;
}
/** @var array<string, array<string, array<string, string>>> $overrides */
$overrides = $medicalReport->input_overrides ?? [];
$coordinateCount = (int) collect(Arr::get($overrides, $departmentKey, []))
->sum(fn (array $sheetValues): int => count($sheetValues));
$structuredCount = (int) collect(Arr::get($overrides, 'structured_departments.'.$departmentKey, []))
->sum(function (array $template): int {
$directEntries = count(array_filter(
$template['entries'] ?? [],
fn (array $entry): bool => $this->entryHasValue($entry),
));
$sectionEntries = (int) collect($template['sections'] ?? [])
->sum(fn (array $section): int => count(array_filter(
$section['entries'] ?? [],
fn (array $entry): bool => $this->entryHasValue($entry),
)));
return $directEntries + $sectionEntries;
});
return $coordinateCount + $structuredCount;
}
/**
* @return array<string, mixed>|null
*/
private function structuredTemplate(MedicalReport $medicalReport, string $departmentKey): ?array
{
$template = $this->structuredTemplateRegistry->templateForDepartment($departmentKey);
if ($template === null) {
return null;
}
$entries = Arr::get(
$medicalReport->input_overrides ?? [],
'structured_departments.'.$departmentKey.'.'.$template['key'].'.entries',
[],
);
if (isset($template['sections']) && is_array($template['sections'])) {
return [
...$template,
'sections' => array_map(function (array $section) use ($medicalReport, $departmentKey, $template): array {
$entries = Arr::get(
$medicalReport->input_overrides ?? [],
'structured_departments.'.$departmentKey.'.'.$template['key'].'.sections.'.$section['key'].'.entries',
[],
);
$resolvedEntries = $entries === []
? ($section['default_entries'] !== [] ? $section['default_entries'] : [$section['empty_entry']])
: $entries;
return [
...$section,
'entries' => $resolvedEntries,
'totals' => $this->structuredTotals(
$resolvedEntries,
$section['fields'] ?? [],
),
];
}, $template['sections']),
];
}
return [
...$template,
'entries' => $entries === []
? ($template['default_entries'] !== [] ? $template['default_entries'] : [$template['empty_entry']])
: $entries,
'totals' => $this->structuredTotals(
$entries === []
? ($template['default_entries'] !== [] ? $template['default_entries'] : [$template['empty_entry']])
: $entries,
$template['fields'] ?? [],
),
];
}
/**
* @param array<string, mixed> $template
*/
private function structuredEditableCount(array $template): int
{
if (isset($template['sections']) && is_array($template['sections'])) {
return (int) collect($template['sections'])
->sum(fn (array $section): int => count($section['entries'] ?? []) * count($section['fields'] ?? []));
}
return (int) count($template['entries'] ?? []) * count($template['fields'] ?? []);
}
/**
* @param array<string, mixed> $entry
*/
private function entryHasValue(array $entry): bool
{
return collect($entry)
->filter(fn (mixed $value): bool => trim((string) $value) !== '')
->isNotEmpty();
}
/**
* @param list<array<string, mixed>> $entries
* @param list<array<string, mixed>> $fields
* @return list<array{key: string, label: string, value: string}>
*/
private function structuredTotals(array $entries, array $fields): array
{
return collect($fields)
->filter(fn (array $field): bool => ($field['type'] ?? 'text') === 'number')
->map(function (array $field) use ($entries): array {
$sum = collect($entries)->sum(function (array $entry) use ($field): float {
$value = trim((string) ($entry[$field['key']] ?? ''));
if ($value === '') {
return 0;
}
return (float) str_replace(',', '.', $value);
});
return [
'key' => $field['key'],
'label' => (string) $field['label'],
'value' => $this->formatNumber($sum),
];
})
->values()
->all();
}
private function formatNumber(float $value): string
{
if ((float) ((int) $value) === $value) {
return (string) ((int) $value);
}
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
}
}