Перевод на доменную архитектуру
This commit is contained in:
33
app/Domain/Reports/Calculators/BedDaysCalculator.php
Normal file
33
app/Domain/Reports/Calculators/BedDaysCalculator.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Calculators;
|
||||
|
||||
use App\Domain\Reports\Models\MetricAggregate;
|
||||
use App\Domain\Reports\Models\StayInterval;
|
||||
|
||||
final class BedDaysCalculator
|
||||
{
|
||||
/**
|
||||
* @param iterable<StayInterval> $intervals
|
||||
*/
|
||||
public function calculate(iterable $intervals): MetricAggregate
|
||||
{
|
||||
$totalDays = 0;
|
||||
$patientCount = 0;
|
||||
|
||||
foreach ($intervals as $interval) {
|
||||
if ($interval->endAt < $interval->startAt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalDays += $interval->startAt->setTime(0, 0)->diff($interval->endAt->setTime(0, 0))->days;
|
||||
$patientCount++;
|
||||
}
|
||||
|
||||
return new MetricAggregate(
|
||||
total: $totalDays,
|
||||
count: $patientCount,
|
||||
average: $patientCount > 0 ? round($totalDays / $patientCount, 2) : 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
15
app/Domain/Reports/Calculators/DepartmentLoadCalculator.php
Normal file
15
app/Domain/Reports/Calculators/DepartmentLoadCalculator.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Calculators;
|
||||
|
||||
final class DepartmentLoadCalculator
|
||||
{
|
||||
public function calculate(int|float $currentCount, int|float $bedsCount): int
|
||||
{
|
||||
if ($bedsCount <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) round($currentCount * 100 / $bedsCount);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Calculators;
|
||||
|
||||
use App\Domain\Reports\Models\MetricAggregate;
|
||||
use App\Domain\Reports\Models\OperationInterval;
|
||||
|
||||
final class PreoperativeDaysCalculator
|
||||
{
|
||||
/**
|
||||
* @param iterable<OperationInterval> $intervals
|
||||
*/
|
||||
public function calculate(iterable $intervals): MetricAggregate
|
||||
{
|
||||
$totalDays = 0;
|
||||
$patientCount = 0;
|
||||
|
||||
foreach ($intervals as $interval) {
|
||||
if ($interval->operationAt < $interval->admittedAt) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalDays += $interval->admittedAt->setTime(0, 0)->diff($interval->operationAt->setTime(0, 0))->days;
|
||||
$patientCount++;
|
||||
}
|
||||
|
||||
return new MetricAggregate(
|
||||
total: $totalDays,
|
||||
count: $patientCount,
|
||||
average: $patientCount > 0 ? round($totalDays / $patientCount, 1) : 0.0,
|
||||
);
|
||||
}
|
||||
}
|
||||
10
app/Domain/Reports/Contracts/AuditLogger.php
Normal file
10
app/Domain/Reports/Contracts/AuditLogger.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Contracts;
|
||||
|
||||
use App\Application\Reports\DTO\ReportComparisonResult;
|
||||
|
||||
interface AuditLogger
|
||||
{
|
||||
public function logComparison(ReportComparisonResult $result): void;
|
||||
}
|
||||
12
app/Domain/Reports/Contracts/MetricCalculator.php
Normal file
12
app/Domain/Reports/Contracts/MetricCalculator.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Contracts;
|
||||
|
||||
use App\Domain\Reports\Models\MetricResultCollection;
|
||||
use App\Domain\Reports\Models\PatientCollection;
|
||||
use App\Domain\Reports\Models\ReportContext;
|
||||
|
||||
interface MetricCalculator
|
||||
{
|
||||
public function calculate(ReportContext $context, PatientCollection $patients): MetricResultCollection;
|
||||
}
|
||||
11
app/Domain/Reports/Contracts/PatientSource.php
Normal file
11
app/Domain/Reports/Contracts/PatientSource.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Contracts;
|
||||
|
||||
use App\Domain\Reports\Models\PatientCollection;
|
||||
use App\Domain\Reports\Models\ReportContext;
|
||||
|
||||
interface PatientSource
|
||||
{
|
||||
public function load(ReportContext $context): PatientCollection;
|
||||
}
|
||||
13
app/Domain/Reports/Contracts/ReportRepository.php
Normal file
13
app/Domain/Reports/Contracts/ReportRepository.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Contracts;
|
||||
|
||||
use App\Domain\Reports\Models\ReportSnapshot;
|
||||
use App\Domain\Reports\Models\SavedReportResult;
|
||||
|
||||
interface ReportRepository
|
||||
{
|
||||
public function save(ReportSnapshot $snapshot): SavedReportResult;
|
||||
|
||||
public function findSnapshot(int $reportId): ?ReportSnapshot;
|
||||
}
|
||||
12
app/Domain/Reports/Models/MetricAggregate.php
Normal file
12
app/Domain/Reports/Models/MetricAggregate.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
final readonly class MetricAggregate
|
||||
{
|
||||
public function __construct(
|
||||
public int $total,
|
||||
public int $count,
|
||||
public float $average,
|
||||
) {}
|
||||
}
|
||||
23
app/Domain/Reports/Models/MetricResultCollection.php
Normal file
23
app/Domain/Reports/Models/MetricResultCollection.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
use App\Domain\Reports\ValueObjects\MetrikaConfig;
|
||||
|
||||
final readonly class MetricResultCollection
|
||||
{
|
||||
/**
|
||||
* @param array<int|string, int|float|string|null> $metrics
|
||||
*/
|
||||
public function __construct(
|
||||
public array $metrics = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<int, int|float|string|null>
|
||||
*/
|
||||
public function normalized(): array
|
||||
{
|
||||
return MetrikaConfig::normalizeMetrics($this->metrics);
|
||||
}
|
||||
}
|
||||
13
app/Domain/Reports/Models/OperationInterval.php
Normal file
13
app/Domain/Reports/Models/OperationInterval.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class OperationInterval
|
||||
{
|
||||
public function __construct(
|
||||
public DateTimeImmutable $admittedAt,
|
||||
public DateTimeImmutable $operationAt,
|
||||
) {}
|
||||
}
|
||||
20
app/Domain/Reports/Models/PatientCollection.php
Normal file
20
app/Domain/Reports/Models/PatientCollection.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
final readonly class PatientCollection
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $items
|
||||
* @param array<string, mixed> $metadata
|
||||
*/
|
||||
public function __construct(
|
||||
public array $items = [],
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
|
||||
public function metadata(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return $this->metadata[$key] ?? $default;
|
||||
}
|
||||
}
|
||||
21
app/Domain/Reports/Models/ReportContext.php
Normal file
21
app/Domain/Reports/Models/ReportContext.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class ReportContext
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $metadata
|
||||
*/
|
||||
public function __construct(
|
||||
public int $departmentId,
|
||||
public int $userId,
|
||||
public ?int $actorUserId,
|
||||
public DateTimeImmutable $periodStart,
|
||||
public DateTimeImmutable $periodEnd,
|
||||
public string $reportType = 'daily',
|
||||
public array $metadata = [],
|
||||
) {}
|
||||
}
|
||||
85
app/Domain/Reports/Models/ReportSnapshot.php
Normal file
85
app/Domain/Reports/Models/ReportSnapshot.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
use App\Domain\Reports\ValueObjects\MetrikaConfig;
|
||||
use DateTimeImmutable;
|
||||
use InvalidArgumentException;
|
||||
|
||||
final readonly class ReportSnapshot
|
||||
{
|
||||
/**
|
||||
* @param array<int|string, int|float|string|null> $metrics
|
||||
* @param array<int, array<string, mixed>> $observationPatients
|
||||
* @param array<int, array<string, mixed>> $unwantedEvents
|
||||
*/
|
||||
public function __construct(
|
||||
public int $departmentId,
|
||||
public int $userId,
|
||||
public ?int $actorUserId,
|
||||
public DateTimeImmutable $periodStart,
|
||||
public DateTimeImmutable $periodEnd,
|
||||
public string $status = 'draft',
|
||||
public bool $autoFill = false,
|
||||
public array $metrics = [],
|
||||
public array $observationPatients = [],
|
||||
public array $unwantedEvents = [],
|
||||
public ?int $reportId = null,
|
||||
public ?DateTimeImmutable $createdAt = null,
|
||||
public ?DateTimeImmutable $sentAt = null,
|
||||
public string $reportType = 'daily',
|
||||
) {
|
||||
if ($this->departmentId <= 0) {
|
||||
throw new InvalidArgumentException('departmentId must be positive.');
|
||||
}
|
||||
|
||||
if ($this->userId <= 0) {
|
||||
throw new InvalidArgumentException('userId must be positive.');
|
||||
}
|
||||
|
||||
if ($this->periodEnd < $this->periodStart) {
|
||||
throw new InvalidArgumentException('periodEnd must not be earlier than periodStart.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, int|float|string|null>
|
||||
*/
|
||||
public function normalizedMetrics(): array
|
||||
{
|
||||
return MetrikaConfig::normalizeMetrics($this->metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int|float|string|null>
|
||||
*/
|
||||
public function payloadMetrics(): array
|
||||
{
|
||||
return MetrikaConfig::toPayloadMetrics($this->normalizedMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toComparableArray(): array
|
||||
{
|
||||
$observationPatients = $this->observationPatients;
|
||||
$unwantedEvents = $this->unwantedEvents;
|
||||
|
||||
sort($observationPatients);
|
||||
sort($unwantedEvents);
|
||||
|
||||
return [
|
||||
'department_id' => $this->departmentId,
|
||||
'user_id' => $this->userId,
|
||||
'actor_user_id' => $this->actorUserId,
|
||||
'period_start' => $this->periodStart->format('Y-m-d H:i:s'),
|
||||
'period_end' => $this->periodEnd->format('Y-m-d H:i:s'),
|
||||
'status' => $this->status,
|
||||
'auto_fill' => $this->autoFill,
|
||||
'metrics' => $this->normalizedMetrics(),
|
||||
'observation_patients' => $observationPatients,
|
||||
'unwanted_events' => $unwantedEvents,
|
||||
];
|
||||
}
|
||||
}
|
||||
11
app/Domain/Reports/Models/SavedReportResult.php
Normal file
11
app/Domain/Reports/Models/SavedReportResult.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
final readonly class SavedReportResult
|
||||
{
|
||||
public function __construct(
|
||||
public int $reportId,
|
||||
public ReportSnapshot $snapshot,
|
||||
) {}
|
||||
}
|
||||
13
app/Domain/Reports/Models/StayInterval.php
Normal file
13
app/Domain/Reports/Models/StayInterval.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\Models;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class StayInterval
|
||||
{
|
||||
public function __construct(
|
||||
public DateTimeImmutable $startAt,
|
||||
public DateTimeImmutable $endAt,
|
||||
) {}
|
||||
}
|
||||
100
app/Domain/Reports/ValueObjects/MetrikaConfig.php
Normal file
100
app/Domain/Reports/ValueObjects/MetrikaConfig.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Reports\ValueObjects;
|
||||
|
||||
final class MetrikaConfig
|
||||
{
|
||||
public const BEDS = 1;
|
||||
|
||||
public const RECIPIENT = 3;
|
||||
|
||||
public const PLAN = 4;
|
||||
|
||||
public const OUTCOME = 7;
|
||||
|
||||
public const CURRENT = 8;
|
||||
|
||||
public const DECEASED = 9;
|
||||
|
||||
public const EMERGENCY_SURGERY = 10;
|
||||
|
||||
public const PLAN_SURGERY = 11;
|
||||
|
||||
public const EMERGENCY = 12;
|
||||
|
||||
public const TRANSFERRED = 13;
|
||||
|
||||
public const OBSERVATION = 14;
|
||||
|
||||
public const DISCHARGED = 15;
|
||||
|
||||
public const UNWANTED_EVENTS = 16;
|
||||
|
||||
public const AVERAGE_BED_DAYS = 18;
|
||||
|
||||
public const PREOPERATIVE_AVERAGE_DAYS = 21;
|
||||
|
||||
public const DEPARTMENT_LOADED = 22;
|
||||
|
||||
public const TOTAL_BED_DAYS = 25;
|
||||
|
||||
public const TOTAL_PREOPERATIVE_DAYS = 26;
|
||||
|
||||
public const PREOPERATIVE_PATIENT_COUNT = 27;
|
||||
|
||||
public static function payloadKey(int $metricId): string
|
||||
{
|
||||
return 'metrika_item_'.$metricId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int|string, int|float|string|null> $metrics
|
||||
* @return array<int, int|float|string|null>
|
||||
*/
|
||||
public static function normalizeMetrics(array $metrics): array
|
||||
{
|
||||
$normalized = [];
|
||||
|
||||
foreach ($metrics as $key => $value) {
|
||||
$metricId = self::extractMetricId($key);
|
||||
|
||||
if ($metricId === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$normalized[$metricId] = $value;
|
||||
}
|
||||
|
||||
ksort($normalized);
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int|float|string|null> $metrics
|
||||
* @return array<string, int|float|string|null>
|
||||
*/
|
||||
public static function toPayloadMetrics(array $metrics): array
|
||||
{
|
||||
$payload = [];
|
||||
|
||||
foreach (self::normalizeMetrics($metrics) as $metricId => $value) {
|
||||
$payload[self::payloadKey($metricId)] = $value;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public static function extractMetricId(int|string $key): ?int
|
||||
{
|
||||
if (is_int($key) || ctype_digit((string) $key)) {
|
||||
return (int) $key;
|
||||
}
|
||||
|
||||
if (preg_match('/^metrika_item_(\d+)$/', (string) $key, $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) $matches[1];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user