Перевод на доменную архитектуру

This commit is contained in:
brusnitsyn
2026-04-26 23:37:50 +09:00
parent 75ca01ffd8
commit f107ebd167
70 changed files with 4656 additions and 2070 deletions

View File

@@ -0,0 +1,143 @@
<?php
namespace App\Application\Reports;
use App\Application\Reports\DTO\GenerateReportInput;
use App\Application\Reports\DTO\GenerateReportResult;
use App\Application\Reports\DTO\ReportComparisonResult;
use App\Domain\Reports\Contracts\AuditLogger;
use App\Domain\Reports\Contracts\MetricCalculator;
use App\Domain\Reports\Contracts\PatientSource;
use App\Domain\Reports\Contracts\ReportRepository;
use App\Domain\Reports\Models\PatientCollection;
use App\Domain\Reports\ValueObjects\MetrikaConfig;
use DateTimeImmutable;
final readonly class GenerateReportUseCase
{
/**
* @param iterable<MetricCalculator> $calculators
*/
public function __construct(
private ReportRepository $reportRepository,
private AuditLogger $auditLogger,
private CompareLegacyAndNewReportUseCase $comparator,
private ?PatientSource $patientSource = null,
private iterable $calculators = [],
private bool $compareBeforeCutover = true,
) {}
public function handle(GenerateReportInput $input): GenerateReportResult
{
$resolvedInput = $this->resolveInput($input);
$saved = $this->reportRepository->save($resolvedInput->toSnapshot());
$comparison = null;
if ($this->compareBeforeCutover) {
try {
$comparison = $this->comparator->handle(
$resolvedInput->withPersistedReportId($saved->reportId)
);
} catch (\Throwable $exception) {
$comparison = new ReportComparisonResult(
reportType: $resolvedInput->reportType,
path: 'new',
status: 'failed',
diff: ['error' => $exception->getMessage()],
departmentId: $resolvedInput->departmentId,
userId: $resolvedInput->userId,
periodStart: $resolvedInput->periodStart->format('Y-m-d H:i:s'),
periodEnd: $resolvedInput->periodEnd->format('Y-m-d H:i:s'),
reportId: $saved->reportId,
durationMs: 0.0,
);
}
$this->auditLogger->logComparison($comparison);
}
return new GenerateReportResult(
reportId: $saved->reportId,
path: 'new',
usedNewArchitecture: true,
comparison: $comparison,
);
}
private function resolveInput(GenerateReportInput $input): GenerateReportInput
{
if ($input->rawPayload !== null) {
return $input;
}
if ($this->patientSource === null) {
return $input;
}
$patients = $this->patientSource->load($input->toContext());
$payload = $patients->metadata('payload', []);
if (! is_array($payload) || $payload === []) {
$payload = $this->buildPayloadFromCalculators($input, $patients);
}
return new GenerateReportInput(
departmentId: $input->departmentId,
userId: $input->userId,
actorUserId: $input->actorUserId,
periodStart: $input->periodStart,
periodEnd: $input->periodEnd,
metrics: (array) ($payload['metrics'] ?? $input->metrics),
observationPatients: (array) ($payload['observationPatients'] ?? $input->observationPatients),
unwantedEvents: (array) ($payload['unwantedEvents'] ?? $input->unwantedEvents),
reportId: isset($payload['reportId']) ? (int) $payload['reportId'] : $input->reportId,
status: (string) ($payload['status'] ?? $input->status),
reportType: $input->reportType,
autoFill: $input->autoFill,
rawPayload: $payload,
persistedReportId: $input->persistedReportId,
createdAt: isset($payload['created_at'])
? new DateTimeImmutable((string) $payload['created_at'])
: $input->createdAt,
sentAt: isset($payload['sent_at'])
? new DateTimeImmutable((string) $payload['sent_at'])
: $input->sentAt,
);
}
/**
* Переходный fallback для будущих сценариев с calculator-based flow.
*
* @return array<string, mixed>
*/
private function buildPayloadFromCalculators(GenerateReportInput $input, PatientCollection $patients): array
{
$metrics = $input->metrics;
foreach ($this->calculators as $calculator) {
$metrics = [
...$metrics,
...$calculator->calculate($input->toContext(), $patients)->normalized(),
];
}
return [
'departmentId' => $input->departmentId,
'userId' => $input->userId,
'actorUserId' => $input->actorUserId,
'autoFill' => $input->autoFill,
'dates' => [
$input->periodStart->getTimestamp(),
$input->periodEnd->getTimestamp(),
],
'status' => $input->status,
'created_at' => $input->createdAt?->format('Y-m-d H:i:s') ?? $input->periodEnd->format('Y-m-d H:i:s'),
'sent_at' => $input->sentAt?->format('Y-m-d H:i:s') ?? $input->periodEnd->format('Y-m-d H:i:s'),
'metrics' => MetrikaConfig::toPayloadMetrics($metrics),
'observationPatients' => $input->observationPatients,
'unwantedEvents' => $input->unwantedEvents,
'reportId' => $input->reportId,
];
}
}