310 lines
11 KiB
PHP
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'), '.');
|
|
}
|
|
}
|