first commit
This commit is contained in:
309
app/Support/MedicalReport/ReportWorkbook.php
Normal file
309
app/Support/MedicalReport/ReportWorkbook.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?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'), '.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user