1138 lines
46 KiB
PHP
1138 lines
46 KiB
PHP
<?php
|
||
|
||
use App\Models\Department;
|
||
use App\Models\HospitalUnit;
|
||
use App\Models\MedicalReport;
|
||
use App\Models\MedicalReportFormTemplate;
|
||
use App\Models\User;
|
||
use App\Support\MedicalReport\StructuredTemplateRegistry;
|
||
use App\Support\MedicalReport\TemplateWorkbook;
|
||
use Database\Seeders\DepartmentSeeder;
|
||
use Database\Seeders\HospitalUnitSeeder;
|
||
use Database\Seeders\ProfileSeeder;
|
||
|
||
test('guests are redirected to the login page for medical reports', function () {
|
||
$response = $this->get(route('medical-reports.index'));
|
||
|
||
$response->assertRedirect(route('login'));
|
||
});
|
||
|
||
test('authenticated users can open the medical report workspace', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create([
|
||
'name' => 'МЕД 2026',
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$departmentKey = $templateWorkbook->departments()[0]['key'];
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.show', [
|
||
'medicalReport' => $report,
|
||
'department' => $departmentKey,
|
||
]));
|
||
|
||
$response
|
||
->assertOk()
|
||
->assertInertia(
|
||
fn ($page) => $page
|
||
->component('medical-reports/Show')
|
||
->where('report.id', $report->id)
|
||
->where('selectedDepartment', $departmentKey)
|
||
->has('departments')
|
||
->has('sheets')
|
||
);
|
||
});
|
||
|
||
test('medical report workspace exposes source driven departments', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create([
|
||
'input_overrides' => [],
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Рентгенологическое отделение');
|
||
|
||
expect($department)->not->toBeNull();
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.show', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertInertia(
|
||
fn ($page) => $page
|
||
->where(
|
||
'departments',
|
||
fn ($departments) => collect($departments)->contains(
|
||
fn (array $department): bool => $department['name'] === 'Рентгенологическое отделение'
|
||
&& ! empty($department['sources'])
|
||
&& in_array($department['sources'][0]['extension'], ['pdf', 'xlsx'], true)
|
||
)
|
||
)
|
||
->where('structuredTemplate.title', 'Рентгенологическое отделение')
|
||
->has('structuredTemplate.sections', 5)
|
||
);
|
||
});
|
||
|
||
test('authenticated users can open the economist summary page', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create([
|
||
'name' => 'МЕД 2026',
|
||
'input_overrides' => [],
|
||
]);
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.economists', $report));
|
||
|
||
$response
|
||
->assertOk()
|
||
->assertInertia(
|
||
fn ($page) => $page
|
||
->component('medical-reports/Economists')
|
||
->where('report.id', $report->id)
|
||
->has('summarySheet.columns')
|
||
->has('summarySheet.rows')
|
||
);
|
||
});
|
||
|
||
test('authenticated users can open the form builder page', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$departmentKey = $templateWorkbook->departments()[0]['key'];
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $departmentKey,
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->component('medical-reports/Builder')
|
||
->where('selectedDepartment', $departmentKey)
|
||
->where('builder.department.report_input_type', fn ($value) => $value === null || is_string($value))
|
||
->has('builder.template.schema.sections')
|
||
);
|
||
});
|
||
|
||
test('builder uses configured report input blueprint for reporting units', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.department.report_input_type', 'department_two_metrics')
|
||
->where('builder.starter_source', 'blueprint')
|
||
->where('builder.template.schema.sections.0.title', 'ОМС по отделениям')
|
||
->where('builder.template.schema.sections.1.title', 'Договорные исследования')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Лаб. ед.')
|
||
->where('builder.template.schema.sections.1.fields.0.label', 'Организация')
|
||
->where('builder.template.schema.sections.0.default_entries', fn ($entries) => count($entries) === 32)
|
||
->where('builder.template.schema.sections.1.default_entries.0.organization_name', 'Мицар')
|
||
);
|
||
});
|
||
|
||
test('kdl builder blueprint exposes separate sections for emergency planned and immunology reports', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Клинико-диагностическая лаборатория');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.department.report_input_type', 'department_multi_metrics')
|
||
->where('builder.template.schema.sections.0.title', 'Экстренные анализы')
|
||
->where('builder.template.schema.sections.1.title', 'Плановая служба')
|
||
->where('builder.template.schema.sections.2.title', 'Иммунологические исследования')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Биох.')
|
||
->where('builder.template.schema.sections.0.fields.3.label', 'Гематология')
|
||
->where('builder.template.schema.sections.2.fields.1.label', 'Мелк. инф.')
|
||
->where('builder.template.schema.sections.2.fields.8.label', 'Иммуноглобулины')
|
||
);
|
||
});
|
||
|
||
test('functional diagnostics builder blueprint exposes refined report section titles and fields', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение функциональной диагностики');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Лаборатория клинической нейрофизиологии')
|
||
->where('builder.template.schema.sections.0.fields.2.label', 'Эхо-Эс')
|
||
->where('builder.template.schema.sections.0.fields.3.label', 'Холтер')
|
||
->where('builder.template.schema.sections.0.fields.4.label', 'АОКП')
|
||
->where('builder.template.schema.sections.1.title', 'РСЦ и сосудистые исследования')
|
||
->where('builder.template.schema.sections.2.title', 'ПДЦ / стационар и исследования на месте')
|
||
);
|
||
});
|
||
|
||
test('endoscopy structured template blueprint exposes load and broncho sections', function () {
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Эндоскопическое отделение');
|
||
|
||
$template = $structuredTemplateRegistry->templateForDepartment($department['key']);
|
||
|
||
expect($template)->not->toBeNull();
|
||
expect(data_get($template, 'sections.0.title'))->toBe('Нагрузка по отделениям стационара и поликлиники');
|
||
expect(data_get($template, 'sections.1.title'))->toBe('Бронхокабинет / отдельный учет');
|
||
expect(data_get($template, 'sections.0.fields.1.label'))->toBe('Гастроскопия / число иссл.');
|
||
expect(data_get($template, 'sections.1.fields.5.label'))->toBe('Всего бронхоскопий');
|
||
});
|
||
|
||
test('pathology builder blueprint exposes inpatient ambulatory and autopsy sections with refined labels', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Патологоанатомическое отделение');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Стационар / гистологические исследования')
|
||
->where('builder.template.schema.sections.1.title', 'Амбулаторно / внешние источники')
|
||
->where('builder.template.schema.sections.2.fields.0.label', 'Всего аутопсий')
|
||
->where('builder.template.schema.sections.2.fields.3.label', 'Кусочки при вскрытии')
|
||
);
|
||
});
|
||
|
||
test('transfusion builder blueprint exposes department load labels', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение трансфузиологии');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Трансфузионная нагрузка по отделениям')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Количество трансфузий')
|
||
->where('builder.template.schema.sections.0.fields.2.label', 'Плазмаферез')
|
||
->where('builder.template.schema.sections.0.default_entries', fn ($entries) => count($entries) === 29)
|
||
);
|
||
});
|
||
|
||
test('main reanimation builder blueprint exposes department movement report fields', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение анестезиологии-реанимации с палатами реанимации и интенсивной терапии');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Движение больных по отделениям')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Больные')
|
||
->where('builder.template.schema.sections.0.fields.2.label', 'Проведено койко-дней')
|
||
);
|
||
});
|
||
|
||
test('opc reanimation builder blueprint exposes unit bed days rows and monthly total', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение анестезиологии-реанимации с палатами реанимации и интенсивной терапии Областного перинатального центра');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Койко-дни по отделениям ОПЦ')
|
||
->where('builder.template.schema.sections.0.default_entries.0.unit_name', 'Родильное отделение')
|
||
->where('builder.template.schema.sections.0.default_entries.1.unit_name', 'Патология беременных')
|
||
->where('builder.template.schema.sections.1.title', 'Итого за месяц')
|
||
->where('builder.template.schema.sections.1.default_entries.0.label', 'Всего')
|
||
);
|
||
});
|
||
|
||
test('rsc reanimation builder blueprint exposes department movement report fields', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение анестезиологии-реанимации с палатами реанимации и интенсивной терапии Регионального сосудистого центра');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Движение больных по отделениям РСЦ')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Больные')
|
||
->where('builder.template.schema.sections.0.fields.2.label', 'Проведено койко-дней')
|
||
);
|
||
});
|
||
|
||
test('operation block builder blueprint exposes monthly totals and operations catalog sections', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Операционный блок');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.department.report_input_type', 'service_catalog_channels')
|
||
->where('builder.template.schema.sections.0.title', 'Свод по отделениям за месяц')
|
||
->where('builder.template.schema.sections.1.title', 'Профили и блоки операций')
|
||
->where('builder.template.schema.sections.2.title', 'Операции')
|
||
->where('builder.template.schema.sections.2.fields.0.label', 'Профиль')
|
||
->where('builder.template.schema.sections.2.fields.1.label', 'Наименование операции')
|
||
);
|
||
});
|
||
|
||
test('radiology builder blueprint uses section-specific row labels', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Рентгенологическое отделение');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.fields.0.label', 'Направивший специалист')
|
||
->where('builder.template.schema.sections.1.fields.0.label', 'Направивший специалист')
|
||
->where('builder.template.schema.sections.2.fields.0.label', 'Отделение / источник')
|
||
->where('builder.template.schema.sections.3.fields.0.label', 'Исследование')
|
||
);
|
||
});
|
||
|
||
test('rhmdil builder blueprint exposes monthly totals thyroid tab and interventions catalog sections', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'РХМДиЛ');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.department.report_input_type', 'service_catalog_channels')
|
||
->where('builder.template.schema.sections.0.title', 'Свод по отделениям за месяц')
|
||
->where('builder.template.schema.sections.1.title', 'ТАБ щитовидной железы')
|
||
->where('builder.template.schema.sections.2.title', 'Каталог вмешательств')
|
||
->where('builder.template.schema.sections.1.fields.1.label', 'ОМС / пациенты')
|
||
->where('builder.template.schema.sections.2.fields.0.label', 'Группа')
|
||
);
|
||
});
|
||
|
||
test('maternity builder blueprint exposes deliveries operations and opu section with note field', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Родильное отделение');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Количество пациентов по подразделениям')
|
||
->where('builder.template.schema.sections.1.title', 'Источники поступления пациентов')
|
||
->where('builder.template.schema.sections.2.title', 'Роды, операции и ОПУ')
|
||
->where('builder.template.schema.sections.2.fields.5.label', 'Примечание')
|
||
);
|
||
});
|
||
|
||
test('cso builder blueprint exposes main and opc sterilization sections', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Централизованное стерилизационное отделение');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Основное ЦСО')
|
||
->where('builder.template.schema.sections.1.title', 'ЦСО ОПЦ')
|
||
->where('builder.template.schema.sections.0.fields.1.label', 'Количество упаковок')
|
||
->where('builder.template.schema.sections.1.fields.1.label', 'Количество биксов и пакетов')
|
||
);
|
||
});
|
||
|
||
test('uzd pdc builder blueprint exposes departments and methods sections', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение ультразвуковой диагностики');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Исследования по отделениям')
|
||
->where('builder.template.schema.sections.1.title', 'Свод по методикам')
|
||
->where('builder.template.schema.sections.1.fields.0.label', 'Методика')
|
||
->where('builder.template.schema.sections.0.default_entries', fn ($entries) => count($entries) === 32)
|
||
->where('builder.template.schema.sections.1.default_entries.0.method_name', 'ЦДК')
|
||
);
|
||
});
|
||
|
||
test('uzd opc builder blueprint exposes polyclinic stationary and monthly total sections', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Отделение ультразвуковой диагностики ОПЦ');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.builder', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertOk()->assertInertia(
|
||
fn ($page) => $page
|
||
->where('builder.template.schema.sections.0.title', 'Кабинеты УЗД поликлиники ОПЦ')
|
||
->where('builder.template.schema.sections.1.title', 'Кабинеты УЗД стационара ОПЦ')
|
||
->where('builder.template.schema.sections.2.title', 'Итого за месяц')
|
||
->where('builder.template.schema.sections.2.fields.0.label', 'Строка')
|
||
->where('builder.template.schema.sections.0.default_entries.0.cabinet_group', 'Каб. №2177, №2178')
|
||
->where('builder.template.schema.sections.2.default_entries.0.label', 'Итого за месяц')
|
||
);
|
||
});
|
||
|
||
test('kdl bacteriology department uses structured row template with database department options', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$option = Department::factory()->create([
|
||
'name' => 'ОТП',
|
||
'code' => 'otp',
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
|
||
expect($department)->not->toBeNull();
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->get(route('medical-reports.show', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$response->assertInertia(
|
||
fn ($page) => $page
|
||
->where('structuredTemplate.title', 'Бактериологическая лаборатория')
|
||
->where(
|
||
'structuredTemplate.department_options',
|
||
fn ($options) => collect($options)->contains(
|
||
fn (array $item): bool => $item['id'] === $option->id
|
||
)
|
||
)
|
||
->where('structuredTemplate.sections.0.fields.1.label', 'Лаб. ед.')
|
||
);
|
||
});
|
||
|
||
test('structured department rows are persisted for a medical report', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$option = Department::factory()->create([
|
||
'name' => 'ОТП',
|
||
'code' => 'otp',
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
$template = $structuredTemplateRegistry->templateForDepartment($department['key']);
|
||
$fieldKeys = collect($template['sections'][0]['fields'])->pluck('key')->values();
|
||
$sectionKey = $template['sections'][0]['key'];
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->put(route('medical-reports.update-structured', $report), [
|
||
'department' => $department['key'],
|
||
'template_key' => $template['key'],
|
||
'sections' => [
|
||
$sectionKey => [
|
||
'entries' => [
|
||
[
|
||
$fieldKeys[0] => $option->id,
|
||
$fieldKeys[1] => '297.0',
|
||
$fieldKeys[2] => 103,
|
||
],
|
||
],
|
||
],
|
||
],
|
||
]);
|
||
|
||
$response->assertRedirect();
|
||
|
||
expect(data_get(
|
||
$report->fresh()->input_overrides,
|
||
'structured_departments.'.$department['key'].'.'.$template['key'].'.sections.'.$sectionKey.'.entries',
|
||
))->toBe([
|
||
[
|
||
$fieldKeys[0] => (string) $option->id,
|
||
$fieldKeys[1] => '297.0',
|
||
$fieldKeys[2] => '103',
|
||
],
|
||
]);
|
||
});
|
||
|
||
test('section totals are persisted for radiology and exposed to economists', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Рентгенологическое отделение');
|
||
$template = $structuredTemplateRegistry->templateForDepartment($department['key']);
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->put(route('medical-reports.update-structured', $report), [
|
||
'department' => $department['key'],
|
||
'template_key' => $template['key'],
|
||
'sections' => [
|
||
'adult_polyclinic' => [
|
||
'entries' => [
|
||
[
|
||
'study_name' => 'Терапевт',
|
||
'units' => '10.5',
|
||
'people_count' => '2',
|
||
'research_count' => '3',
|
||
'copies_count' => '1',
|
||
'barium' => '0',
|
||
'contrast' => '0',
|
||
],
|
||
[
|
||
'study_name' => 'Хирург',
|
||
'units' => '4.5',
|
||
'people_count' => '1',
|
||
'research_count' => '2',
|
||
'copies_count' => '0',
|
||
'barium' => '0',
|
||
'contrast' => '1',
|
||
],
|
||
],
|
||
],
|
||
'child_polyclinic' => ['entries' => []],
|
||
'inpatient' => ['entries' => []],
|
||
'ct_115' => ['entries' => []],
|
||
'ct_125' => ['entries' => []],
|
||
],
|
||
]);
|
||
|
||
$response->assertRedirect();
|
||
|
||
expect(data_get(
|
||
$report->fresh()->input_overrides,
|
||
'structured_departments.'.$department['key'].'.'.$template['key'].'.sections.adult_polyclinic.entries',
|
||
))->toBe([
|
||
[
|
||
'study_name' => 'Терапевт',
|
||
'units' => '10.5',
|
||
'people_count' => '2',
|
||
'research_count' => '3',
|
||
'copies_count' => '1',
|
||
'barium' => '0',
|
||
'contrast' => '0',
|
||
],
|
||
[
|
||
'study_name' => 'Хирург',
|
||
'units' => '4.5',
|
||
'people_count' => '1',
|
||
'research_count' => '2',
|
||
'copies_count' => '0',
|
||
'barium' => '0',
|
||
'contrast' => '1',
|
||
],
|
||
]);
|
||
|
||
$economistResponse = $this->get(route('medical-reports.economists', $report));
|
||
|
||
$economistResponse->assertInertia(
|
||
fn ($page) => $page
|
||
->where('summarySheet.name', 'Итоги отделений')
|
||
->where(
|
||
'summarySheet.rows',
|
||
fn ($rows) => collect($rows)->contains(
|
||
fn (array $row): bool => $row['department'] === 'Рентгенологическое отделение / Поликлиника взрослая'
|
||
&& collect($row['values'])->contains(
|
||
fn (array $value): bool => $value['name'] === 'Рентген / усл. ед.' && $value['value'] === '15'
|
||
)
|
||
&& collect($row['values'])->contains(
|
||
fn (array $value): bool => $value['name'] === 'Рентген / исследования' && $value['value'] === '5'
|
||
)
|
||
)
|
||
)
|
||
);
|
||
});
|
||
|
||
test('builder templates can be saved and then used by report input page', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$response = $this->put(route('medical-reports.upsert-builder', $report), [
|
||
'department' => $department['key'],
|
||
'name' => 'КДЛ форма',
|
||
'description' => 'Конструктор',
|
||
'schema' => [
|
||
'title' => 'КДЛ форма',
|
||
'description' => 'Конструктор',
|
||
'sections' => [
|
||
[
|
||
'key' => 'main',
|
||
'title' => 'Основной блок',
|
||
'economist_label' => 'КДЛ / Основной блок',
|
||
'fields' => [
|
||
['key' => 'department_id', 'label' => 'Отделение', 'type' => 'department-select'],
|
||
['key' => 'lab_units', 'label' => 'Лаб. ед.', 'type' => 'number'],
|
||
],
|
||
'export_metrics' => [
|
||
[
|
||
'key' => 'lab_units_total',
|
||
'label' => 'Итого лаб. ед.',
|
||
'source_field' => 'lab_units',
|
||
'aggregation' => 'sum',
|
||
],
|
||
],
|
||
'default_entries' => [
|
||
['department_id' => '', 'lab_units' => '0'],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
]);
|
||
|
||
$response->assertRedirect();
|
||
|
||
$template = MedicalReportFormTemplate::query()
|
||
->where('department_key', $department['key'])
|
||
->first();
|
||
$hospitalUnit = HospitalUnit::query()
|
||
->where('slug', $department['key'])
|
||
->first();
|
||
|
||
expect($template)->not->toBeNull();
|
||
expect($hospitalUnit)->not->toBeNull();
|
||
expect($template?->hospital_unit_id)->toBe($hospitalUnit?->id);
|
||
expect(data_get($template?->schema, 'title'))->toBe('КДЛ форма');
|
||
expect(data_get($template?->schema, 'sections.0.key'))->toBe('main');
|
||
expect(data_get($template?->schema, 'sections.0.fields.0'))->toBe([
|
||
'key' => 'department_id',
|
||
'label' => 'Отделение',
|
||
'type' => 'department-select',
|
||
]);
|
||
expect(data_get($template?->schema, 'sections.0.fields.1'))->toBe([
|
||
'key' => 'lab_units',
|
||
'label' => 'Лаб. ед.',
|
||
'type' => 'number',
|
||
]);
|
||
expect(data_get($template?->schema, 'sections.0.export_metrics.0'))->toBe([
|
||
'key' => 'lab_units_total',
|
||
'label' => 'Итого лаб. ед.',
|
||
'source_field' => 'lab_units',
|
||
'aggregation' => 'sum',
|
||
'analysis_column' => null,
|
||
'row_mode' => null,
|
||
'target_unit_slug' => null,
|
||
]);
|
||
|
||
$showResponse = $this->get(route('medical-reports.show', [
|
||
'medicalReport' => $report,
|
||
'department' => $department['key'],
|
||
]));
|
||
|
||
$showResponse->assertInertia(
|
||
fn ($page) => $page
|
||
->where('structuredTemplate.title', 'КДЛ форма')
|
||
->where('structuredTemplate.sections.0.title', 'Основной блок')
|
||
->where('structuredTemplate.sections.0.fields.1.label', 'Лаб. ед.')
|
||
);
|
||
});
|
||
|
||
test('economist summary uses configured export metrics from builder templates', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
$option = Department::factory()->create([
|
||
'name' => 'ОТП',
|
||
'code' => 'otp',
|
||
]);
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
$department = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
|
||
$this->actingAs($user);
|
||
|
||
$this->put(route('medical-reports.upsert-builder', $report), [
|
||
'department' => $department['key'],
|
||
'name' => 'КДЛ форма',
|
||
'description' => 'Конструктор',
|
||
'schema' => [
|
||
'title' => 'КДЛ форма',
|
||
'description' => 'Конструктор',
|
||
'sections' => [
|
||
[
|
||
'key' => 'main',
|
||
'title' => 'Основной блок',
|
||
'economist_label' => 'Бак лаб / Основной блок',
|
||
'fields' => [
|
||
['key' => 'department_id', 'label' => 'Отделение', 'type' => 'department-select'],
|
||
['key' => 'lab_units', 'label' => 'Лаб. ед.', 'type' => 'number'],
|
||
['key' => 'research_count', 'label' => 'Исследования', 'type' => 'number'],
|
||
],
|
||
'export_metrics' => [
|
||
[
|
||
'key' => 'lab_units_total',
|
||
'label' => 'Итого лаб. ед.',
|
||
'source_field' => 'lab_units',
|
||
'aggregation' => 'sum',
|
||
],
|
||
],
|
||
'default_entries' => [
|
||
['department_id' => '', 'lab_units' => '0', 'research_count' => '0'],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
])->assertRedirect();
|
||
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
$template = $structuredTemplateRegistry->templateForDepartment($department['key']);
|
||
|
||
$this->put(route('medical-reports.update-structured', $report), [
|
||
'department' => $department['key'],
|
||
'template_key' => $template['key'],
|
||
'sections' => [
|
||
'main' => [
|
||
'entries' => [
|
||
[
|
||
'department_id' => $option->id,
|
||
'lab_units' => '297.0',
|
||
'research_count' => '103',
|
||
],
|
||
],
|
||
],
|
||
],
|
||
])->assertRedirect();
|
||
|
||
$economistResponse = $this->get(route('medical-reports.economists', $report));
|
||
|
||
$economistResponse->assertInertia(
|
||
fn ($page) => $page
|
||
->where(
|
||
'summarySheet.columns',
|
||
fn ($columns) => collect($columns)->contains(
|
||
fn (array $column): bool => $column['name'] === 'Итого лаб. ед.'
|
||
)
|
||
)
|
||
->where(
|
||
'summarySheet.rows',
|
||
fn ($rows) => collect($rows)->contains(
|
||
fn (array $row): bool => $row['department'] === 'Бак лаб / Основной блок'
|
||
&& $row['values'][0]['value'] === '297'
|
||
)
|
||
)
|
||
);
|
||
});
|
||
|
||
test('economist analysis matrix maps department rows through hospital unit links and blueprint export metrics', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
|
||
$bacteriologyDepartment = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
$physioDepartment = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Физиотерапевтическое отделение');
|
||
|
||
$mappedDepartment = Department::query()
|
||
->where('name', 'Гематология')
|
||
->firstOrFail();
|
||
|
||
$bacteriologyTemplate = $structuredTemplateRegistry->templateForDepartment($bacteriologyDepartment['key']);
|
||
$physioTemplate = $structuredTemplateRegistry->templateForDepartment($physioDepartment['key']);
|
||
|
||
$report->update([
|
||
'input_overrides' => [
|
||
'structured_departments' => [
|
||
$bacteriologyDepartment['key'] => [
|
||
$bacteriologyTemplate['key'] => [
|
||
'sections' => [
|
||
'oms_departments' => [
|
||
'entries' => [
|
||
[
|
||
'department_id' => (string) $mappedDepartment->id,
|
||
'lab_units' => '15',
|
||
'research_count' => '7',
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
$physioDepartment['key'] => [
|
||
$physioTemplate['key'] => [
|
||
'sections' => [
|
||
'main' => [
|
||
'entries' => [
|
||
[
|
||
'department_id' => (string) $mappedDepartment->id,
|
||
'physio_people' => '1',
|
||
'physio_procedures' => '2',
|
||
'physio_units' => '3',
|
||
'massage_people' => '4',
|
||
'massage_procedures' => '5',
|
||
'massage_units' => '6',
|
||
'exercise_people' => '7',
|
||
'exercise_procedures' => '8',
|
||
'exercise_units' => '9',
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
]);
|
||
|
||
$this->actingAs($user);
|
||
|
||
$economistResponse = $this->get(route('medical-reports.economists', $report));
|
||
|
||
$economistResponse->assertInertia(
|
||
fn ($page) => $page
|
||
->where('analysisIssues.total_count', 0)
|
||
->where(
|
||
'analysisSheet.rows',
|
||
fn ($rows) => collect($rows)->contains(function (array $row): bool {
|
||
if ($row['unit_name'] !== 'Гематологическое отделение') {
|
||
return false;
|
||
}
|
||
|
||
$values = collect($row['values'])->mapWithKeys(
|
||
fn (array $value): array => [$value['key'] => $value['value']]
|
||
);
|
||
|
||
return $values->get('bak_lab_research') === '7'
|
||
&& $values->get('bak_lab_units') === '15'
|
||
&& $values->get('fto_procedures') === '15'
|
||
&& $values->get('fto_units') === '18';
|
||
})
|
||
)
|
||
);
|
||
});
|
||
|
||
test('economist page exposes unmapped department rows as analysis issues', function () {
|
||
$user = User::factory()->create();
|
||
$report = MedicalReport::factory()->create();
|
||
|
||
$this->seed([
|
||
ProfileSeeder::class,
|
||
HospitalUnitSeeder::class,
|
||
DepartmentSeeder::class,
|
||
]);
|
||
|
||
/** @var TemplateWorkbook $templateWorkbook */
|
||
$templateWorkbook = app(TemplateWorkbook::class);
|
||
/** @var StructuredTemplateRegistry $structuredTemplateRegistry */
|
||
$structuredTemplateRegistry = app(StructuredTemplateRegistry::class);
|
||
|
||
$bacteriologyDepartment = collect($templateWorkbook->departments())
|
||
->firstWhere('name', 'Бактериологическая лаборатория');
|
||
$bacteriologyTemplate = $structuredTemplateRegistry->templateForDepartment($bacteriologyDepartment['key']);
|
||
$unmappedDepartment = Department::query()
|
||
->where('name', 'ОТП')
|
||
->firstOrFail();
|
||
|
||
$report->update([
|
||
'input_overrides' => [
|
||
'structured_departments' => [
|
||
$bacteriologyDepartment['key'] => [
|
||
$bacteriologyTemplate['key'] => [
|
||
'sections' => [
|
||
'oms_departments' => [
|
||
'entries' => [
|
||
[
|
||
'department_id' => (string) $unmappedDepartment->id,
|
||
'lab_units' => '2',
|
||
'research_count' => '1',
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
],
|
||
]);
|
||
|
||
$this->actingAs($user);
|
||
|
||
$economistResponse = $this->get(route('medical-reports.economists', $report));
|
||
|
||
$economistResponse->assertInertia(
|
||
fn ($page) => $page
|
||
->where('analysisIssues.total_count', 1)
|
||
->where(
|
||
'analysisIssues.unmapped_departments',
|
||
fn ($issues) => collect($issues)->contains(
|
||
fn (array $issue): bool => $issue['department'] === 'Бактериологическая лаборатория'
|
||
&& $issue['section'] === 'ОМС по отделениям'
|
||
&& $issue['source_department'] === 'ОТП'
|
||
)
|
||
)
|
||
);
|
||
});
|