chore: initial import for test contour

This commit is contained in:
sova-bootstrap
2026-05-27 19:36:32 +03:00
commit 166cdb148e
282 changed files with 84872 additions and 0 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,84 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Specialist;
use App\Service\Bitrix\BitrixService;
use Psr\Log\LoggerInterface;
#[AsCommand(
name: 'bitrix-update-doctors',
description: 'Обновления врачей из CMS Bitrix',
)]
class BitrixUpdateDoctorsCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private EntityManagerInterface $entityManager,
private BitrixService $bitrixService
)
{
parent::__construct();
}
protected function configure(): void
{
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$this->logger->info('start: bitrix-update-doctors');
$io->info('Началось выполнение.');
$specialistList = $this->entityManager->getRepository(Specialist::class)->findAll();
$count = 0;
foreach ($specialistList as $specialist) {
$dcodes = $specialist->getDcodes();
if (empty($dcodes) || $dcodes === '0' || $dcodes === 0) {
$specialist->setDcodes(null);
} else {
$dcodesArray = explode(',', $dcodes);
$filteredDcodes = array_filter(
array_unique($dcodesArray),
function($item) {
return strlen(trim($item)) >= 7 && trim($item) !== '0';
}
);
if (empty($filteredDcodes)) {
$specialist->setDcodes(null);
} else {
$specialist->setDcodes(implode(',', $filteredDcodes));
}
}
// $kodoper = $this->bitrixService->getServiceCode($specialist->getId());
// if (!empty($kodoper)) {
// $specialist->setKodoper($kodoper);
// $this->entityManager->persist($specialist);
// }
$count ++;
}
$this->entityManager->flush();
$this->logger->info('end: bitrix-update-doctors ' . $count);
$io->success('load: ' . $count);
return Command::SUCCESS;
}
}
+243
View File
@@ -0,0 +1,243 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Specialist;
use App\Entity\Review;
use App\Service\Bitrix\BitrixService;
use Psr\Log\LoggerInterface;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Symfony\Component\Console\Helper\ProgressBar;
#[AsCommand(
name: 'bitrix-update-reviews',
description: 'Обновления отзывов врачей из CMS Bitrix',
)]
final class BitrixUpdateReviewsCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private EntityManagerInterface $entityManager,
private BitrixService $bitrixService
)
{
parent::__construct();
}
protected function configure(): void
{
}
private $review = [
'source' => null,
'rating' => 5
];
private function safeUtf8Clean($text)
{
$result = '';
$length = strlen($text);
for ($i = 0; $i < $length; $i++) {
$byte = ord($text[$i]);
// Валидные ASCII символы
if ($byte <= 0x7F) {
// Разрешаем только печатаемые ASCII и управляющие символы
if ($byte >= 0x20 || $byte == 0x09 || $byte == 0x0A || $byte == 0x0D) {
$result .= $text[$i];
}
continue;
}
// Многобайтовые UTF-8 последовательности
if (($byte & 0xE0) == 0xC0) {
// 2-байтовая последовательность
if ($i + 1 < $length) {
$byte2 = ord($text[$i + 1]);
if (($byte2 & 0xC0) == 0x80) {
$result .= $text[$i] . $text[$i + 1];
$i++;
}
}
} elseif (($byte & 0xF0) == 0xE0) {
// 3-байтовая последовательность
if ($i + 2 < $length) {
$byte2 = ord($text[$i + 1]);
$byte3 = ord($text[$i + 2]);
if (($byte2 & 0xC0) == 0x80 && ($byte3 & 0xC0) == 0x80) {
$result .= $text[$i] . $text[$i + 1] . $text[$i + 2];
$i += 2;
}
}
} elseif (($byte & 0xF8) == 0xF0) {
// 4-байтовая последовательность
if ($i + 3 < $length) {
$byte2 = ord($text[$i + 1]);
$byte3 = ord($text[$i + 2]);
$byte4 = ord($text[$i + 3]);
if (($byte2 & 0xC0) == 0x80 && ($byte3 & 0xC0) == 0x80 && ($byte4 & 0xC0) == 0x80) {
$result .= $text[$i] . $text[$i + 1] . $text[$i + 2] . $text[$i + 3];
$i += 3;
}
}
}
}
return $result;
}
private function processName(array $data): void
{
$name = $this->safeUtf8Clean($data['VALUE']);
$this->review['name'] = empty($name)? 'Анонимно': $name;
$this->review['dateCreate'] = \DateTime::createFromFormat('Y-m-d H:i:s', $data['DATE_CREATE']);
$this->review['active'] = ($data['ACTIVE'] == 'Y')? true: false;
}
private function processMessage(string $value): void
{
$message = $this->safeUtf8Clean($value);
$message = preg_replace('/a:\d+:\{.*\}|s:\d+:".*"|\w+:\d+:/', '', $message);
$message = str_replace('{', '', $message);
$message = str_replace('}', '', $message);
$this->review['message'] = trim(strip_tags($message));
}
private function processSourceLink(string $value): void
{
preg_match('/https?:\/\/[^\s<>"\']+/i', $value, $matches);
if (!empty($matches[0])) {
$this->review['source'] = $this->safeUtf8Clean(substr($matches[0], 0, 255));
}
}
private function processRating($value): void
{
$this->review['rating'] = max(1, min(5, $value));
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->info('Началось выполнение.');
$this->logger->info('start: bitrix-update-reviews');
$specialistAll = $this->entityManager->getRepository(Specialist::class)->findAll();
$progressBar = new ProgressBar($output, count($specialistAll));
unset($specialistAll);
$page = 1;
$batchSize = 5;
do {
$progressBar->advance();
$query = $this->entityManager->getRepository(Specialist::class)
->createQueryBuilder('s')
->setFirstResult(($page - 1) * $batchSize)
->setMaxResults($batchSize)
->getQuery();
$paginator = new Paginator($query);
$specialistList = iterator_to_array($paginator);
$page++;
$this->loadData($specialistList);
$this->entityManager->clear();
gc_collect_cycles();
} while (count($specialistList) > 0);
$this->entityManager->clear();
$progressBar->finish();
$output->writeln('');
gc_collect_cycles();
return Command::SUCCESS;
}
private function loadData(array $specialistList)
{
$count = 0;
foreach ($specialistList as $specialist) {
$reviews = $this->bitrixService->getReviews($specialist->getId());
foreach ($reviews as $key => $params) {
$this->review['externalId'] = (int) $params['REVIEW_ID'];
$review = $this->entityManager->getRepository(Review::class)
->findOneBy(['externalId' => $this->review['externalId']]);
foreach ($params['DATA'] as $data) {
$code = $data['CODE'];
$value = $data['VALUE'];
match ($code) {
"NAME" => $this->processName($data),
"MESSAGE" => $this->processMessage($value),
"SOURCE_LINK" => $this->processSourceLink($value),
"RATING" => $this->processRating((int) $value),
default => null // Игнорируем неизвестные коды
};
}
if (!$this->review['active'] || empty($this->review['message'])) continue;
if (!$review) {
$review = new Review;
$review->setExternalId($this->review['externalId']);
}
$review
->setActive($this->review['active'])
->setDateCreate($this->review['dateCreate'])
->setSource($this->review['source'])
->setRating($this->review['rating'])
->setAuthor($this->review['name'])
->setMessage($this->review['message']);
$specialist->addReview($review);
}
try {
$this->entityManager->flush();
} catch (DriverException $e) {
$this->logger->error('Problematic parameters: ' . print_r($this->review, true));
// Проверяем каждый параметр на валидность UTF-8
foreach ($this->review as $index => $param) {
if (is_string($param) && !mb_check_encoding($param, 'UTF-8')) {
$this->logger->error("Invalid UTF-8 in parameter $index: " . bin2hex($param));
}
}
throw $e;
}
$count ++;
}
$this->entityManager->flush();
return true;
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Command;
use App\Service\ScheduleCache\ScheduleCacheService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:schedule:clear-cache',
description: 'Clear old schedule cache'
)]
class ClearScheduleCacheCommand extends Command
{
public function __construct(
private ScheduleCacheService $cacheService
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('hours', null, InputOption::VALUE_REQUIRED, 'Clear cache older than X hours', 24)
->addOption('stats', null, InputOption::VALUE_NONE, 'Show cache statistics')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
if ($input->getOption('stats')) {
$stats = $this->cacheService->getCacheStats();
$io->title('Schedule Cache Statistics');
$io->table(
['Metric', 'Value'],
[
['Total Records', number_format($stats['total_records'])],
['Unique Queries', number_format($stats['unique_queries'])],
['Oldest Record', $stats['oldest_record']->format('Y-m-d H:i:s')],
['Newest Record', $stats['newest_record']->format('Y-m-d H:i:s')]
]
);
if (!empty($stats['last_7_days'])) {
$io->section('Last 7 Days Activity');
$rows = [];
foreach ($stats['last_7_days'] as $day) {
$rows[] = [
$day['day'],
number_format($day['records_count']),
number_format($day['queries_count'])
];
}
$io->table(['Date', 'Records', 'Queries'], $rows);
}
return Command::SUCCESS;
}
$hours = (int)$input->getOption('hours');
$olderThan = new \DateTime(sprintf('-%d hours', $hours));
$io->note(sprintf('Clearing cache older than %s', $olderThan->format('Y-m-d H:i:s')));
try {
$deletedCount = $this->cacheService->clearOldCache($olderThan);
$io->success(sprintf('Successfully cleared %d cache records older than %d hours', $deletedCount, $hours));
return Command::SUCCESS;
} catch (\Exception $e) {
$io->error(sprintf('Error clearing cache: %s', $e->getMessage()));
return Command::FAILURE;
}
}
}
+306
View File
@@ -0,0 +1,306 @@
<?php
namespace App\Command;
use App\Entity\Department;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\Client\Interfaces\InfoclinicaClientServiceInterface;
use App\Service\Translite\Interfaces\TransliteServiceInterface;
#[AsCommand(
name: 'upload:deps',
description: 'Пакетное обновление отделений для врачей',
)]
class UploadDepartmentsCommand extends Command
{
private const BATCH_SIZE = 100;
public function __construct(
private EntityManagerInterface $entityManager,
private InfoclinicaClientServiceInterface $client,
private TransliteServiceInterface $transliteService
) {
parent::__construct();
}
protected function configure(): void { }
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Пакетное обновление отделений');
try {
// Используем InfoclinicaClientServiceInterface для запроса
$httpResponse = $this->client->request('GET', '/specialists/departments');
$responseData = $httpResponse->toArray();
if (empty($responseData['data'])) {
$io->success('Нет данных для обработки');
return Command::SUCCESS;
}
$totalRecords = count($responseData['data']);
$io->info("Загружено записей из API: {$totalRecords}");
// Убираем возможные дубликаты из данных API (по did)
$uniqueData = $this->removeDuplicates($responseData['data']);
$uniqueCount = count($uniqueData);
if ($uniqueCount < $totalRecords) {
$io->note("Удалено дубликатов: " . ($totalRecords - $uniqueCount));
$io->info("Уникальных записей для обработки: {$uniqueCount}");
}
// Пакетная обработка с UPSERT
$processed = $this->processWithUpsert($uniqueData, $io);
// Статистика
$io->table(
['Статистика', 'Значение'],
[
['Всего записей в API', $totalRecords],
['Уникальных записей', $uniqueCount],
['Успешно обработано', $processed],
['Пропущено', $uniqueCount - $processed]
]
);
$io->success('Обработка завершена успешно');
} catch (\Exception $e) {
$io->error('Ошибка: ' . $e->getMessage());
$io->error('Trace: ' . $e->getTraceAsString());
return Command::FAILURE;
}
return Command::SUCCESS;
}
/**
* Удаляет дубликаты по полю id (did)
*/
private function removeDuplicates(array $data): array
{
$unique = [];
foreach ($data as $item) {
if (!isset($item['id'])) {
continue;
}
$unique[$item['id']] = $item; // id из API как ключ для уникальности
}
return array_values($unique);
}
/**
* Основной метод обработки с UPSERT
* Возвращает количество успешно обработанных записей
*/
private function processWithUpsert(array $data, SymfonyStyle $io): int
{
$connection = $this->entityManager->getConnection();
$tableName = $this->entityManager->getClassMetadata(Department::class)->getTableName();
$total = count($data);
$processed = 0;
$skipped = 0;
$io->progressStart($total);
// Обрабатываем пакетами
for ($i = 0; $i < $total; $i += self::BATCH_SIZE) {
$batch = array_slice($data, $i, self::BATCH_SIZE);
try {
$batchProcessed = $this->executeUpsertBatch($connection, $tableName, $batch, $io);
$processed += $batchProcessed;
$io->progressAdvance(count($batch));
// Периодически выводим статистику
if ($i % (self::BATCH_SIZE * 10) === 0 || $i + self::BATCH_SIZE >= $total) {
$io->writeln(sprintf(
' [%s] Обработано: %d/%d (%.1f%%)',
date('H:i:s'),
$processed,
$total,
($processed / $total) * 100
));
}
} catch (\Exception $e) {
$io->warning(sprintf(
'Ошибка в пакете %d-%d: %s',
$i + 1,
min($i + self::BATCH_SIZE, $total),
$e->getMessage()
));
// Если ошибка в пакете, пробуем обработать по одной записи
$batchProcessed = $this->handleBatchError($connection, $tableName, $batch, $io);
$processed += $batchProcessed;
$skipped += (count($batch) - $batchProcessed);
}
}
$io->progressFinish();
if ($skipped > 0) {
$io->warning("Пропущено записей из-за ошибок: {$skipped}");
}
return $processed;
}
/**
* Выполняет UPSERT для пакета данных
* Возвращает количество успешно обработанных записей в пакете
*/
private function executeUpsertBatch($connection, string $tableName, array $batch, SymfonyStyle $io): int
{
if (empty($batch)) {
return 0;
}
$sqlParts = [];
$params = [];
$validItems = 0;
foreach ($batch as $index => $item) {
if (!isset($item['id']) || !isset($item['name'])) {
continue; // Пропускаем некорректные записи
}
$sqlParts[] = sprintf(
'(:did_%d, :name_%d, :alias_%d, :active_%d, :online_mode_%d)',
$index, $index, $index, $index, $index
);
$params['did_' . $index] = (int) $item['id'];
$params['name_' . $index] = $item['name'];
$params['alias_' . $index] = $this->transliteService->translit($item['name']);
$params['active_' . $index] = true;
$params['online_mode_' . $index] = $item['onlineMode'] ?? false;
$validItems++;
}
if (empty($sqlParts)) {
return 0;
}
$valuesSql = implode(', ', $sqlParts);
$updateSql = sprintf(
'UPDATE %1$s AS d
SET
name = src.name,
alias = src.alias,
active = CAST(src.active AS BOOLEAN),
online_mode = CAST(src.online_mode AS BOOLEAN)
FROM (VALUES %2$s) AS src(did, name, alias, active, online_mode)
WHERE d.did = CAST(src.did AS BIGINT)',
$tableName,
$valuesSql
);
$insertSql = sprintf(
'INSERT INTO %1$s (did, name, alias, active, online_mode)
SELECT
CAST(src.did AS BIGINT),
src.name,
src.alias,
CAST(src.active AS BOOLEAN),
CAST(src.online_mode AS BOOLEAN)
FROM (VALUES %2$s) AS src(did, name, alias, active, online_mode)
WHERE NOT EXISTS (
SELECT 1 FROM %1$s d WHERE d.did = CAST(src.did AS BIGINT)
)',
$tableName,
$valuesSql
);
$connection->executeStatement($updateSql, $params);
$connection->executeStatement($insertSql, $params);
return $validItems;
}
/**
* Обрабатывает ошибку пакета, пробуя вставить записи по одной
* Возвращает количество успешно обработанных записей
*/
private function handleBatchError($connection, string $tableName, array $batch, SymfonyStyle $io): int
{
$successCount = 0;
foreach ($batch as $item) {
try {
if ($this->executeSingleUpsert($connection, $tableName, $item)) {
$successCount++;
}
} catch (\Exception $e) {
$io->warning(sprintf(
'Не удалось обработать запись did=%s: %s',
$item['id'] ?? 'unknown',
$e->getMessage()
));
}
}
return $successCount;
}
/**
* Выполняет UPSERT для одной записи
* Возвращает true если успешно
*/
private function executeSingleUpsert($connection, string $tableName, array $item): bool
{
if (!isset($item['id']) || !isset($item['name'])) {
return false;
}
$alias = $this->transliteService->translit($item['name']);
$active = $item['active'] ?? true;
$onlineMode = $item['onlineMode'] ?? false;
$updateSql = sprintf(
'UPDATE %s
SET
name = :name,
alias = :alias,
active = :active,
online_mode = :online_mode
WHERE did = :did',
$tableName
);
$insertSql = sprintf(
'INSERT INTO %s (did, name, alias, active, online_mode)
SELECT :did, :name, :alias, :active, :online_mode
WHERE NOT EXISTS (
SELECT 1 FROM %s WHERE did = :did
)',
$tableName,
$tableName
);
$params = [
'did' => (int) $item['id'],
'name' => $item['name'],
'alias' => $alias,
'active' => $active,
'online_mode' => $onlineMode
];
$connection->executeStatement($updateSql, $params);
$connection->executeStatement($insertSql, $params);
return true;
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Command;
use App\Service\DiseaseCrudService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'upload:diseases',
description: 'Обновление таблицы disease из view_disease',
)]
final class UploadDiseasesCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private DiseaseCrudService $diseaseCrudService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('view', null, InputOption::VALUE_OPTIONAL, 'SQL view name', 'public.view_disease');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$viewName = $input->getOption('view');
if (empty($viewName)) {
$viewName = 'public.view_disease';
}
$viewName = (string) $viewName;
$io->title('Disease: sync from view_disease');
try {
$this->logger->info('Disease sync start', ['view' => $viewName]);
$affected = $this->diseaseCrudService->syncFromViewDisease($viewName);
$io->success('Sync finished. Affected rows: ' . $affected);
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->logger->error('Disease sync failed', [
'view' => $viewName,
'error' => $e->getMessage(),
]);
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
+262
View File
@@ -0,0 +1,262 @@
<?php
namespace App\Command;
use App\Entity\Idoctor;
use App\Entity\Department;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\Client\Interfaces\InfoclinicaClientServiceInterface;
#[AsCommand(
name: 'upload:doctors',
description: 'Пакетное обновление врачей из инфоклиники',
)]
class UploadDoctorsCommand extends Command
{
private const BATCH_SIZE = 150;
private const CHUNK_SIZE = 300;
public function __construct(
private EntityManagerInterface $entityManager,
private InfoclinicaClientServiceInterface $client
) {
parent::__construct();
}
protected function configure(): void
{
$this->addArgument('onlineMode', InputArgument::OPTIONAL, 'Режим онлайн (0 или 1)', 0);
$this->addOption('department', 'd', InputOption::VALUE_OPTIONAL, 'ID конкретного отделения');
$this->addOption('firstrow', 'f', InputOption::VALUE_OPTIONAL, 'Первая строка', 1);
$this->addOption('lastrow', 'l', InputOption::VALUE_OPTIONAL, 'Последняя строка', 900);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Пакетное обновление врачей');
try {
$onlineMode = (bool) $input->getArgument('onlineMode');
$departmentId = (int) $input->getOption('department');
$firstRow = (int) $input->getOption('firstrow');
$lastRow = (int) $input->getOption('lastrow');
$departments = $this->getDepartmentsToProcess($departmentId);
if (empty($departments)) {
$io->warning('Не найдено отделений для обработки');
return Command::SUCCESS;
}
$io->info('Найдено отделений: ' . count($departments));
$io->info('Режим онлайн: ' . ($onlineMode ? 'Да' : 'Нет'));
$totalDoctorsProcessed = 0;
foreach ($departments as $index => $department) {
$io->section('Обработка отделения: ' . $department['name'] . ' (ID: ' . $department['did'] . ')');
$doctorsData = $this->fetchDoctorsData($department['did'], $onlineMode, $firstRow, $lastRow, $io);
if (empty($doctorsData)) {
$io->note('Нет данных врачей для отделения');
continue;
}
$processed = $this->processDoctorsBatch($doctorsData, $department['did'], $onlineMode, $io);
$totalDoctorsProcessed += $processed;
$io->writeln(sprintf(
'Обработано врачей в отделении: %d',
$processed
));
if ($index < count($departments) - 1) {
sleep(1);
}
$this->entityManager->clear();
}
$io->success(sprintf('Обработка завершена. Всего обработано врачей: %d', $totalDoctorsProcessed));
} catch (\Exception $e) {
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
return Command::SUCCESS;
}
private function getDepartmentsToProcess(?int $departmentId): array
{
$repository = $this->entityManager->getRepository(Department::class);
$qb = $repository->createQueryBuilder('d')
->select('d.did AS did, d.name AS name')
->orderBy('d.id', 'ASC');
if ($departmentId) {
$qb->andWhere('d.did = :departmentId')
->setParameter('departmentId', $departmentId);
} else {
$qb->andWhere('d.active = :active')
->setParameter('active', true);
}
$departments = $qb->getQuery()->getArrayResult();
return array_map(static fn (array $department): array => [
'did' => (int) $department['did'],
'name' => (string) $department['name'],
], $departments);
}
private function fetchDoctorsData(int $departmentId, bool $onlineMode, int $firstRow, int $lastRow, SymfonyStyle $io): array
{
$allDoctors = [];
$chunkSize = self::CHUNK_SIZE;
for ($start = $firstRow; $start <= $lastRow; $start += $chunkSize) {
$end = min($start + $chunkSize - 1, $lastRow);
$path = sprintf(
'/specialists/doctors?departments=%d&onlineMode=%d&firstrow=%d&lastrow=%d',
$departmentId,
$onlineMode ? 1 : 0,
$start,
$end
);
try {
$httpResponse = $this->client->request('GET', $path);
$response = $httpResponse->toArray() ?: [];
if (!empty($response['data'])) {
$allDoctors = array_merge($allDoctors, $response['data']);
$io->writeln(sprintf('Загружено врачей: %d (%d-%d)', count($response['data']), $start, $end));
}
if (count($response['data'] ?? []) < $chunkSize) {
break;
}
usleep(200000);
} catch (\Exception $e) {
$io->warning('Ошибка при загрузке данных для отделения ' . $departmentId . ': ' . $e->getMessage());
break;
}
}
return $allDoctors;
}
private function processDoctorsBatch(array $doctorsData, int $departmentId, bool $onlineMode, SymfonyStyle $io): int
{
if (empty($doctorsData)) {
return 0;
}
// Получаем существующих врачей
$existingDoctors = $this->getExistingDoctors($doctorsData, $departmentId, $onlineMode);
$processed = 0;
$batchCount = 0;
foreach ($doctorsData as $index => $doctorData) {
try {
$doctorKey = $this->getDoctorKey($doctorData['dcode'], $departmentId, $onlineMode);
$iDoctor = $existingDoctors[$doctorKey] ?? new Idoctor();
$this->updateDoctorEntity($iDoctor, $doctorData, $departmentId, $onlineMode);
$this->entityManager->persist($iDoctor);
$processed++;
$batchCount++;
if ($batchCount % self::BATCH_SIZE === 0) {
$this->entityManager->flush();
$io->writeln(sprintf('Сохранено пакет: %d врачей', self::BATCH_SIZE));
$batchCount = 0;
}
} catch (\Exception $e) {
$io->warning('Ошибка при обработке врача ' . ($doctorData['dcode'] ?? 'unknown') . ': ' . $e->getMessage());
continue;
}
}
if ($batchCount > 0) {
$this->entityManager->flush();
$io->writeln(sprintf('Сохранено остальных: %d врачей', $batchCount));
}
return $processed;
}
/**
* Получает существующих врачей
*/
private function getExistingDoctors(array $doctorsData, int $departmentId, bool $onlineMode): array
{
$dCodes = array_column($doctorsData, 'dcode');
if (empty($dCodes)) {
return [];
}
$existingDoctors = $this->entityManager->getRepository(Idoctor::class)
->createQueryBuilder('d')
->where('d.dcode IN (:dCodes)')
->andWhere('d.department = :department')
->andWhere('d.onlineMode = :onlineMode')
->setParameter('dCodes', $dCodes)
->setParameter('department', $departmentId)
->setParameter('onlineMode', $onlineMode)
->getQuery()
->getResult();
$result = [];
foreach ($existingDoctors as $doctor) {
$key = $this->getDoctorKey(
$doctor->getDcode(),
$doctor->getDepartment(),
$doctor->getOnlineMode()
);
$result[$key] = $doctor;
}
return $result;
}
/**
* Создает уникальный ключ для врача
*/
private function getDoctorKey(string $dcode, int $departmentId, bool $onlineMode): string
{
return sprintf('%s_%d_%d', $dcode, $departmentId, $onlineMode ? 1 : 0);
}
/**
* Обновляет сущность врача
*/
private function updateDoctorEntity(Idoctor $doctor, array $data, int $departmentId, bool $onlineMode): void
{
$doctor
->setDcode($data['dcode'] ?? '')
->setName($data['name'] ?? '')
->setDepartment($departmentId)
->setFilial($data['filialId'] ?? null)
->setNearestDate($data['nearestDate'] ?? null)
->setOnlineMode($onlineMode);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
namespace App\Command;
use App\Entity\Filial;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\Translite\Interfaces\TransliteServiceInterface;
use Psr\Log\LoggerInterface;
#[AsCommand(
name: 'upload:filials',
description: 'Обнвление филиалов',
)]
class UploadFilialsCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private EntityManagerInterface $entityManager,
private HttpClientInterface $client,
private TransliteServiceInterface $transliteService,
private string $widgetApiUrl,
)
{
parent::__construct();
}
protected function configure(): void { }
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->logger->info('Началось обнвление филиалов');
$io = new SymfonyStyle($input, $output);
$response = $this->client->request('GET', '/filials/list', [
'verify_peer' => false,
'verify_host' => false,
'base_uri' => $this->widgetApiUrl,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'sovamed_bot'
],
]);
$response = $response->toArray();
$io->info('load:' . count($response['data']));
foreach ($response['data'] as $item) {
$filial = $this->entityManager->getRepository(Filial::class)
->findOneBy(['fid' => $item['id']]);
if (is_null($filial)) {
$filial = new Filial();
}
preg_match('/(ул\.\s*[А-я]+(?:\s+[А-я]+)*)\s*,?\s*д\.\s*(\S+)/u', $item['address'], $matches);
if (isset($matches[1]) && isset($matches[2])) {
$street = $matches[1];
$house = $matches[2];
$filial->setShortName("$street,$house");
}
$filial
->setFid($item['id'])
->setName($item['name'])
->setAddress($item['address'])
->setActive(true)
->setRegionId($this->findRegionId($item['address']))
;
$this->entityManager->persist($filial);
}
$this->entityManager->flush();
$io->success('loaded');
return Command::SUCCESS;
}
private function findRegionId($address) {
$cities = [
91 => "Саратов",
92 => "Волгоград",
93 => "Воронеж",
94 => "Краснодар",
];
foreach ($cities as $key => $city) {
if (stripos($address, $city) !== false) {
return $key;
}
}
return null;
}
}
@@ -0,0 +1,61 @@
<?php
namespace App\Command;
use App\Service\MedicalCenterCrudService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'upload:medical-centers',
description: 'Обновление таблицы medical_center из view_centers'
)]
final class UploadMedicalCentersCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private MedicalCenterCrudService $medicalCenterCrudService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('view', null, InputOption::VALUE_OPTIONAL, 'SQL view name', 'public.view_centers');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$viewName = $input->getOption('view');
if (empty($viewName)) {
$viewName = 'public.view_centers';
}
$viewName = (string) $viewName;
$io->title('MedicalCenter: sync from view_centers');
try {
$this->logger->info('MedicalCenter sync start', ['view' => $viewName]);
$affected = $this->medicalCenterCrudService->syncFromViewCenters($viewName);
$io->success('Sync finished. Affected rows: ' . $affected);
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->logger->error('MedicalCenter sync failed', [
'view' => $viewName,
'error' => $e->getMessage(),
]);
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Command;
use App\Service\NewsCrudService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'upload:news',
description: 'Обновление таблицы news из view_news'
)]
final class UploadNewsCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private NewsCrudService $newsCrudService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('view', null, InputOption::VALUE_OPTIONAL, 'SQL view name', 'public.view_news');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$viewName = $input->getOption('view');
if (empty($viewName)) {
$viewName = 'public.view_news';
}
$viewName = (string) $viewName;
$io->title('News: sync from view_news');
try {
$this->logger->info('News sync start', ['view' => $viewName]);
$affected = $this->newsCrudService->syncFromViewNews($viewName);
$io->success('Sync finished. Affected rows: ' . $affected);
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->logger->error('News sync failed', [
'view' => $viewName,
'error' => $e->getMessage(),
]);
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
+181
View File
@@ -0,0 +1,181 @@
<?php
namespace App\Command;
use App\Entity\PriceDepartment;
use App\Entity\PriceList;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Doctrine\ORM\EntityManagerInterface;
#[AsCommand(
name: 'upload:price',
description: 'Обновление цен',
)]
class UploadPriceCommand extends Command
{
private $nosleep;
public function __construct(
private EntityManagerInterface $entityManager,
private HttpClientInterface $client,
private string $widgetApiUrl,
)
{
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('did', InputArgument::OPTIONAL, 'department id update')
->addOption('debug', null, InputOption::VALUE_NONE, 'debug on')
->addOption('nosleep', null, InputOption::VALUE_OPTIONAL, 'sleep true|false default false')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$departments = $this->entityManager->getRepository(PriceDepartment::class)->findAll();
$departmentId = $input->getArgument('did');
$debug = $input->getOption('debug');
$this->nosleep = $input->getOption('nosleep');
if ($departmentId) {
$departments = $this->entityManager->getRepository(PriceDepartment::class)
->findBy(['groupId' => $departmentId]);
}
if (empty($departments)) {
$io->error('No departments found');
return Command::FAILURE;
}
$batchSize = 100;
$processedCount = 0;
foreach ($departments as $department) {
$io->note("Processing department: " . $department->getGroupId());
foreach ($this->getPricelist($department->getGroupId()) as $collection) {
foreach ($collection as $item) {
$priceList = $this->entityManager->getRepository(PriceList::class)
->findOneBy([
'kodoper' => $item['kodoper'],
'speccode' => $item['speccode'],
'filial' => $item['filial'],
'groupId' => $department->getGroupId()
]);
$text = 'update: ';
if (is_null($priceList)) {
$priceList = new PriceList();
$text = 'create: ';
}
$priceList
->setKodoper($item['kodoper'])
->setSchname($item['schname'])
->setSpecname($item['specname'])
->setSpeccode($item['speccode'])
->setPriceInfo($item['priceInfo'])
->setDiscpercent($item['discpercent'])
->setDiscprice($item['discprice'])
->setStructname($item['structname'])
->setFname($item['fname'])
->setFilial($item['filial'])
->setComment($item['comment'])
->setMediaId($item['mediaId'])
->setDateUpdate(new \DateTime())
->setGroupId($department->getGroupId());
$this->entityManager->persist($priceList);
$processedCount++;
// Пакетное сохранение для экономии памяти
if ($processedCount % $batchSize === 0) {
$this->entityManager->flush();
$this->entityManager->clear();
gc_collect_cycles();
if ($debug) {
$io->info("Flushed batch of {$batchSize} records. Memory usage: " .
round(memory_get_usage() / 1024 / 1024, 2) . "MB");
}
}
}
}
// Финальное сохранение для оставшихся записей
$this->entityManager->flush();
$this->entityManager->clear();
if ($debug) {
$io->info('Sleep: 2 sec');
}
if (empty($this->nosleep)) {
sleep(2);
}
}
$io->success('Successful. Total processed: ' . $processedCount);
return Command::SUCCESS;
}
private function getPricelist($depnum)
{
$response = [];
$flag = true;
$firstrow = 1;
$lastrow = 500;
while ($flag) {
try {
$result = $this->client->request('GET', 'pricelist/list', [
'verify_peer' => false,
'verify_host' => false,
'timeout' => 60,
'base_uri' => $this->widgetApiUrl,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'sovamed_bot'
],
'query' => [
'depnum' => $depnum,
'firstrow' => $firstrow,
'lastrow' => $lastrow
],
]);
$firstrow = $lastrow + 1;
$lastrow += 500;
$resultData = $result->toArray(false); // false чтобы избежать преобразования в объекты
if (empty($resultData['data'])) {
$flag = false;
} else {
yield $resultData['data']; // Используем генератор для экономии памяти
}
if (empty($this->nosleep)) {
sleep(3);
}
} catch (\Exception $e) {
$flag = false;
// Логирование ошибки можно добавить при необходимости
}
}
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace App\Command;
use App\Entity\PriceDepartment;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Doctrine\ORM\EntityManagerInterface;
#[AsCommand(
name: 'upload:priceDep',
description: 'Обновление отделений для услуг',
)]
class UploadPriceDepCommand extends Command
{
public function __construct(
private EntityManagerInterface $entityManager,
private HttpClientInterface $client,
private string $widgetApiUrl,
)
{
parent::__construct();
}
protected function configure(): void { }
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$response = $this->client->request('GET', '/pricelist/departments', [
'verify_peer' => false,
'verify_host' => false,
'base_uri' => $this->widgetApiUrl,
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'sovamed_bot'
],
]);
$response = $response->toArray();
foreach ($response['data'] as $item) {
$department = $this->entityManager->getRepository(PriceDepartment::class)
->findOneBy([
'groupId' => $item['id']
]);
if (is_null($department)) {
$department = new PriceDepartment();
}
if (empty($item['viewInWeb'])) {
$item['viewInWeb'] = 0;
}
$department
->setGroupId($item['id'])
->setName($item['name'])
->setViewInWeb($item['viewInWeb'])
->setDoctCount($item['schCount'])
;
$this->entityManager->persist($department);
$this->entityManager->flush();
$io->success('load: '. $department->getId());
}
return Command::SUCCESS;
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Command;
use App\Service\PromoCrudService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'upload:promo',
description: 'Обновление таблицы promo из view_promo'
)]
final class UploadPromoCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private PromoCrudService $promoCrudService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('view', null, InputOption::VALUE_OPTIONAL, 'SQL view name', 'public.view_promo');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$viewName = $input->getOption('view');
if (empty($viewName)) {
$viewName = 'public.view_promo';
}
$viewName = (string) $viewName;
$io->title('Promo: sync from view_promo');
try {
$this->logger->info('Promo sync start', ['view' => $viewName]);
$affected = $this->promoCrudService->syncFromViewPromo($viewName);
$io->success('Sync finished. Affected rows: ' . $affected);
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->logger->error('Promo sync failed', [
'view' => $viewName,
'error' => $e->getMessage(),
]);
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Command;
use App\Service\SiteServiceCrudService;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'upload:site-services',
description: 'Обновление таблицы site_services из public.view_services'
)]
final class UploadSiteServicesCommand extends Command
{
public function __construct(
private LoggerInterface $logger,
private SiteServiceCrudService $siteServiceCrudService,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addOption('view', null, InputOption::VALUE_OPTIONAL, 'SQL view name', 'public.view_services');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$viewName = $input->getOption('view');
if (empty($viewName)) {
$viewName = 'public.view_services';
}
$viewName = (string) $viewName;
$io->title('Site services: sync from view_services');
try {
$this->logger->info('Site services sync start', ['view' => $viewName]);
$affected = $this->siteServiceCrudService->syncFromViewServices($viewName);
$io->success('Sync finished. Affected rows: ' . $affected);
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->logger->error('Site services sync failed', [
'view' => $viewName,
'error' => $e->getMessage(),
]);
$io->error('Ошибка: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
View File
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Article;
use App\Repository\ArticleRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/article')]
final class ArticleController extends AbstractController
{
private const READ_GROUPS = ['article:read'];
private const WRITE_GROUPS = ['article:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Статьи')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'limit', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'alias', in: 'query', schema: new OA\Schema(type: 'string'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'article_list', methods: ['GET'])]
public function list(Request $request, ArticleRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginateWithLegacyMeta($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/alias/{alias}', name: 'article_show_by_alias', methods: ['GET'])]
public function showByAlias(string $alias, ArticleRepository $repository): JsonResponse
{
$article = $repository->findOneByAlias($alias);
if (!$article) {
throw $this->createNotFoundException('Статья не найдена');
}
return $this->crud->read($article, self::READ_GROUPS);
}
#[Route('/{id}', name: 'article_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Article $article): JsonResponse
{
return $this->crud->read($article, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Article::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'article_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, Article::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Article::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'article_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, Article $article): JsonResponse
{
return $this->crud->update($request, $article, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'article_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Article $article): JsonResponse
{
return $this->crud->delete($article);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use App\Service\Client\Interfaces\CalltouchClientServiceInterface;
use App\Dto\CalltouchCreateRequestDto;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\SerializerInterface;
#[IsGranted('ROLE_ADMIN')]
#[Route('/calltouch')]
final class CalltouchController extends AbstractController
{
public function __construct (
private CalltouchClientServiceInterface $calltouchClientService,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) {}
#[Route('/create-lead', name: 'app_calltouch_create-lead', methods: ['POST'])]
public function createLead(CalltouchCreateRequestDto $dto, Request $request): JsonResponse
{
$requestData = $request->request->all();
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json(['errors' => (string) $errors], 400);
}
// $response = $this->calltouchClientService->requestCreate($dto);
return $this->json([
'request' => $requestData,
// 'data' => $response,
]);
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
final class DefaultController extends AbstractController
{
#[Route('/', name: 'app_default_comingsoon', methods: ['GET'])]
public function comingsoon(): Response
{
return $this->render('service/comingsoon.html.twig', [
'title' => 'Account disabled by server administrator',
'message' => 'Account disabled by server administrator',
]);
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace App\Controller;
use App\Entity\Department;
use App\Repository\DepartmentRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use OpenApi\Attributes as OA;
#[Route('/department')]
final class DepartmentController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) { }
#[OA\Tag(name: 'Список отделений')]
#[Route('/list', name: 'department_list', methods: ['GET'])]
public function list(DepartmentRepository $repository): JsonResponse
{
$departmentList = $repository->activeAll();
return $this->json([
'data' => $departmentList
], Response::HTTP_OK, [], [
'groups' => ['department:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/{did}',
name: 'department_update',
methods: ['PUT'],
requirements: ['did' => '\d+']
)]
public function update(int $did, Request $request, DepartmentRepository $repository): JsonResponse
{
$department = $repository->findOneBy(['did' => $did]);
if (!$department) {
return new JsonResponse([
'message' => 'Department not found'
], Response::HTTP_NOT_FOUND);
}
$this->serializer->deserialize(
$request->getContent(),
Department::class,
'json',
[
'groups' => ['department:write'],
'object_to_populate' => $department
]
);
$errors = $this->validator->validate($department);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($department, Response::HTTP_OK, [], [
'groups' => ['department:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/create', name: 'department_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$department = $this->serializer->deserialize(
$request->getContent(),
Department::class,
'json',
['groups' => ['department:write']]
);
$errors = $this->validator->validate($department);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($department);
$this->em->flush();
return $this->json($department, Response::HTTP_CREATED, [], [
'groups' => ['department:read']
]);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Disease;
use App\Repository\DiseaseRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/disease')]
final class DiseaseController extends AbstractController
{
private const READ_GROUPS = ['disease:read'];
private const WRITE_GROUPS = ['disease:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Заболевания')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'perPage', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'disease_list', methods: ['GET'])]
public function list(Request $request, DiseaseRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/{id}', name: 'disease_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Disease $disease): JsonResponse
{
return $this->crud->read($disease, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Disease::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'disease_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, Disease::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Disease::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'disease_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, Disease $disease): JsonResponse
{
return $this->crud->update($request, $disease, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'disease_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Disease $disease): JsonResponse
{
return $this->crud->delete($disease);
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace App\Controller;
use App\Entity\Filial;
use App\Repository\FilialRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use App\Service\FileUploader\Interfaces\FileUploaderServiceInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Service\Image\Interfaces\ImageServiceInterface;
use App\Dto\FileUploadDto;
use OpenApi\Attributes as OA;
use Exception;
#[Route('/filial')]
final class FilialController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer,
private ImageServiceInterface $imageService
) { }
#[OA\Tag(name: 'Список филиалов')]
#[Route(path: '/list', name: 'filial_list', methods: ['GET'])]
public function list(Request $request, FilialRepository $repository): JsonResponse
{
$regionId = $request->query->getInt('regionId', 0);
$criteria = ['active' => true];
if ($regionId > 0) {
$criteria['regionId'] = $regionId;
}
$filials = $repository->findBy($criteria);
return $this->json(['data' => $filials],
Response::HTTP_OK, [], [
'groups' => ['filial:read']
]);
}
#[Route(
path: '/by-region/{regionId}',
name: 'filial_by_region',
methods: ['GET'],
requirements: ['regionId' => '\d+']
)]
public function byRegion(int $regionId, FilialRepository $repository): JsonResponse
{
$filials = $repository->findBy([
'regionId' => $regionId,
'active' => true
]);
return $this->json(['data' => $filials]
, Response::HTTP_OK, [], [
'groups' => ['filial:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/{fid}',
name: 'filial_update',
methods: ['PUT'],
requirements: ['fid' => '\d+']
)]
public function update(int $fid, Request $request, FilialRepository $repository): JsonResponse
{
$filial = $repository->findOneBy(['fid' => $fid]);
if (!$filial) {
return new JsonResponse([
'message' => 'Filial not found'
], Response::HTTP_NOT_FOUND);
}
$this->serializer->deserialize(
$request->getContent(),
Filial::class,
'json',
[
'groups' => ['filial:write'],
'object_to_populate' => $filial
]
);
$errors = $this->validator->validate($filial);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($filial, Response::HTTP_OK, [], [
'groups' => ['filial:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/create', name: 'filial_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$filial = $this->serializer->deserialize(
$request->getContent(),
Filial::class,
'json',
['groups' => ['filial:write']]
);
$errors = $this->validator->validate($filial);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($filial);
$this->em->flush();
return $this->json($filial, Response::HTTP_CREATED, [], [
'groups' => ['filial:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/picture/{id}', name: 'filial_upload_picture', methods: ['POST'], requirements: ['id' => '\d+'])]
public function pictureUpload(
Filial $filial,
Request $request,
FileUploadDto $dto,
FileUploaderServiceInterface $fileUploader
): JsonResponse {
try {
$uploadedFile = $request->files->get('picture');
if (!$uploadedFile instanceof UploadedFile) {
return $this->json(['error' => 'File not uploaded'], 400);
}
$dto->file = $uploadedFile;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json($errors, 400);
}
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $filial->getPicture());
$fileUploader->setTargetDirectory('filial');
$fileName = $fileUploader->upload($uploadedFile);
$filial->setPicture('filial/'. $fileName);
$this->em->persist($filial);
$this->em->flush();
$this->em->clear();
return $this->json($filial, 200, [], [
'groups' => ['filial:read']
]);
} catch (Exception $e) {
return $this->json([
'error' => 'File upload failed',
'message' => $e->getMessage()
], 500);
}
}
#[Route('/picture/{id}', name: 'filial_picture', methods: ['GET'])]
public function filialPicture(Filial $filial, Request $request): Response
{
$height = min($request->query->getInt('height', 300), 800);
$width = min($request->query->getInt('width', 300), 600);
$uploadDir = $this->getParameter('upload_directory');
return $this->imageService->getPicture($uploadDir . '/'. $filial->getPicture(), $width, $height);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Controller;
use App\Service\Helper\HelperService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
#[Route('/helper')]
final class HelperController extends AbstractController
{
public function __construct(
private HelperService $helperService
) { }
#[Route('/text-year', name: 'service_textYear', methods: ['GET'])]
public function textYear(Request $request): JsonResponse
{
$year = $request->query->getInt('year', 0);
$exp = $request->query->get('exp', '0') === '1';
$response = $this->helperService->textYear($year, $exp);
return $this->json(['data' => $response]);
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
namespace App\Controller;
use App\Exception\ClientRequestException;
use App\Dto\AnonymousReserveRequestDto;
use App\Repository\IdoctorRepository;
use App\Service\Interfaces\InfoclinicaServiceInterfaces;
use App\Service\Specialist\Interfaces\SpecialistServiceInterface;
use App\Service\DecoderJWT\Interfaces\JWTDecoderServiceInterface;
use Pagerfanta\Pagerfanta;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Serializer\SerializerInterface;
use App\Entity\MarkKiosk;
use Doctrine\ORM\EntityManagerInterface;
final class InfoclinicaController extends AbstractController
{
public function __construct(
private ValidatorInterface $validator,
private SpecialistServiceInterface $specialistService,
private SerializerInterface $serializer,
private JWTDecoderServiceInterface $jwtDecoderService
) { }
#[IsGranted('ROLE_USER')]
#[Route('/infoclinica/clvisitsovacheckpass/{filial}', name: 'clvisitsovacheckpass', requirements: ['filial' => '\d+'], methods: ['GET'])]
public function clvisitsovacheckpass(
int $filial,
Request $request,
EntityManagerInterface $entityManager
): JsonResponse {
// Получаем авторизованного пользователя
$user = $this->jwtDecoderService->getUser();
if (!$user) {
return $this->json([
'error' => 'Пользователь не найден'
], 401);
}
$pcode = $user->getUid();
// Получаем репозиторий
$markKioskRepository = $entityManager->getRepository(MarkKiosk::class);
// Проверяем, существует ли уже запись
$markKiosk = $markKioskRepository->findOneBy([
'pcode' => $pcode,
'filial' => $filial
]);
// Если записи нет, создаем новую
if (!$markKiosk) {
$markKiosk = new MarkKiosk();
$now = new \DateTimeImmutable();
$markKiosk->setPcode($pcode);
$markKiosk->setFilial($filial);
$markKiosk->setCreatedAt($now);
$markKiosk->setModifyAt($now);
$entityManager->persist($markKiosk);
$entityManager->flush();
}
$markKiosk = $markKioskRepository->findOneBy([
'pcode' => $pcode,
'filial' => $filial
]);
return $this->json($markKiosk->isResult());
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/idoctor/list', name: 'ic_specialist_list', methods: ['GET'])]
public function list(Request $request, IdoctorRepository $repository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 500), 500);
$qb = $repository->createFilteredQueryBuilder(
$request->query->all()
);
// Создаем адаптер и пагинатор
$adapter = new QueryAdapter($qb);
$pagerfanta = new Pagerfanta($adapter);
// Устанавливаем текущую страницу и количество элементов на странице
$pagerfanta->setMaxPerPage($perPage);
$pagerfanta->setCurrentPage($page);
// Получаем элементы для текущей страницы
$data = $pagerfanta->getCurrentPageResults();
// Формируем ответ с метаданными пагинации
$response = [
'data' => $data,
'pagination' => [
'total' => $pagerfanta->getNbResults(),
'count' => count($data),
'per_page' => $pagerfanta->getMaxPerPage(),
'current_page' => $pagerfanta->getCurrentPage(),
'total_pages' => $pagerfanta->getNbPages(),
'has_previous_page' => $pagerfanta->hasPreviousPage(),
'has_next_page' => $pagerfanta->hasNextPage(),
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['idoctor:read']
]);
}
#[Route('/reservation/anonymous-reserve', name: 'ic_anonymous_reserve', methods: ['POST'])]
public function bookingAnonymous(Request $request): JsonResponse
{
try {
$bookingDto = $this->serializer->deserialize(
$request->getContent(),
AnonymousReserveRequestDto::class,
'json'
);
$errors = $this->validator->validate($bookingDto);
if (count($errors) > 0) {
return $this->json(['errors' => (string) $errors], 400);
}
$reserve = $this->specialistService->createAnonymousReserve($bookingDto);
return $this->json($reserve, $reserve['status_code'] ?? 200);
} catch (\InvalidArgumentException $e) {
return $this->json([
'error' => 'Validation failed',
'details' => $e->getMessage()
], 400);
}
}
}
+148
View File
@@ -0,0 +1,148 @@
<?php
namespace App\Controller;
use App\Entity\Location;
use App\Entity\Specialist;
use App\Repository\LocationRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\Sequence\SequenceService;
use Exception;
#[IsGranted('ROLE_ADMIN')]
final class LocationController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) { }
#[Route(path: '/locations/empty', name: 'location_empty', methods: ['GET'])]
public function empty(LocationRepository $repository): JsonResponse
{
$locations = $repository->findBy(['specialist' => null]);
return $this->json([
'data' => $locations
], Response::HTTP_OK, [], [
'groups' => ['location:read', 'to.specialist:read']
]);
}
#[Route(
path: '/specialist/{id}/location/create',
name: 'locaion_create',
methods: ['POST'],
requirements: ['id' => '\d+']
)]
public function create(
Specialist $specialist,
Request $request,
SequenceService $sequenceService
): JsonResponse {
$debugInfo = $sequenceService->debugSequence(Location::class);
$sequenceService->syncSequence(Location::class);
$this->em->clear();
$location = $this->serializer->deserialize(
$request->getContent(),
Location::class,
'json',
[
'groups' => ['location:write'],
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'
]
);
$location->setSpecialist($specialist);
$errors = $this->validator->validate($location);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($location);
$this->em->flush();
return $this->json($location, Response::HTTP_CREATED, [], [
'groups' => ['location:read', 'to.specialist:read']
]);
}
#[Route(
path: '/specialist/{specialistId}/location/{id}',
name: 'location_update',
methods: ['PUT'],
requirements: ['id' => '\d+', 'specialistId' => '\d+']
)]
public function update(Request $request, int $specialistId, Location $location): JsonResponse
{
try {
$this->serializer->deserialize(
$request->getContent(),
Location::class,
'json',
[
'groups' => ['location:write'],
'object_to_populate' => $location,
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'
]
);
$specialist = $this->em->getRepository(Specialist::class)->find($specialistId);
if (!$specialist) {
return new JsonResponse([
'error' => 'Специалист не найден'
], Response::HTTP_NOT_FOUND);
}
$location->setSpecialist($specialist);
$errors = $this->validator->validate($location);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($location, Response::HTTP_OK, [], [
'groups' => ['location:read', 'to.specialist:read']
]);
} catch (\Throwable $e) {
return new JsonResponse(
['error' => 'Произошла ошибка при обновлении локации'],
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
}
#[Route('/location/{id}', name: 'location_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Location $location): JsonResponse {
try {
$this->em->remove($location);
$this->em->flush();
return new JsonResponse(null, 204);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], 500);
}
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\MedicalCenter;
use App\Repository\MedicalCenterRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/medical-center')]
final class MedicalCenterController extends AbstractController
{
private const READ_GROUPS = ['medical_center:read'];
private const WRITE_GROUPS = ['medical_center:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Центры')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'perPage', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', description: 'Если не передан — фильтр active=true (как в старом API).', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'medical_center_list', methods: ['GET'])]
public function list(Request $request, MedicalCenterRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request, true));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/{id}', name: 'medical_center_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(MedicalCenter $medicalCenter): JsonResponse
{
return $this->crud->read($medicalCenter, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: MedicalCenter::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'medical_center_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, MedicalCenter::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: MedicalCenter::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'medical_center_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, MedicalCenter $medicalCenter): JsonResponse
{
return $this->crud->update($request, $medicalCenter, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'medical_center_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(MedicalCenter $medicalCenter): JsonResponse
{
return $this->crud->delete($medicalCenter);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\News;
use App\Repository\NewsRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/news')]
final class NewsController extends AbstractController
{
private const READ_GROUPS = ['news:read'];
private const WRITE_GROUPS = ['news:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Новости')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'perPage', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', description: 'Если не передан — фильтр active=true (как в старом API).', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'news_list', methods: ['GET'])]
public function list(Request $request, NewsRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request, true));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/{id}', name: 'news_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(News $news): JsonResponse
{
return $this->crud->read($news, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: News::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'news_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, News::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: News::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'news_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, News $news): JsonResponse
{
return $this->crud->update($request, $news, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'news_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(News $news): JsonResponse
{
return $this->crud->delete($news);
}
}
@@ -0,0 +1,30 @@
<?php
namespace App\Controller;
use App\Repository\PriceDepartmentRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use OpenApi\Attributes as OA;
#[Route('/pricelist')]
final class PriceDepartmentController extends AbstractController
{
#[OA\Tag(name: 'Список отделений для услуг и цен')]
#[Route('/department', name: 'department_price_list', methods: ['GET'])]
public function department(PriceDepartmentRepository $repository): JsonResponse
{
$departmentList = $repository->findAll();
return $this->json([
'data' => $departmentList
], Response::HTTP_OK, [], [
'groups' => ['departmentPrice:read']
]);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Repository\PriceListRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use OpenApi\Attributes as OA;
#[Route('/pricelist')]
final class PriceListController extends AbstractController
{
#[OA\Tag(name: 'Услуги и цены')]
#[OA\Parameter(
name: 'page',
in: 'query',
description: 'номер станицы',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'perPage',
in: 'query',
description: 'количество записей',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'filial',
in: 'query',
description: 'id филиала',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'search',
in: 'query',
description: 'поиск по названию или коду услуги',
schema: new OA\Schema(type: 'string')
)]
#[Route('/list', name: 'pricelist_read_all', methods: ['GET'])]
public function readAll(Request $request, PriceListRepository $repository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$qb = $repository->createFilteredQueryBuilder(
$request->query->all()
);
$adapter = new QueryAdapter($qb);
$pagerfanta = new Pagerfanta($adapter);
$pagerfanta->setMaxPerPage($perPage);
$pagerfanta->setCurrentPage($page);
$data = $pagerfanta->getCurrentPageResults();
$response = [
'data' => $data,
'pagination' => [
'total' => $pagerfanta->getNbResults(),
'count' => count($data),
'per_page' => $pagerfanta->getMaxPerPage(),
'current_page' => $pagerfanta->getCurrentPage(),
'total_pages' => $pagerfanta->getNbPages(),
'has_previous_page' => $pagerfanta->hasPreviousPage(),
'has_next_page' => $pagerfanta->hasNextPage(),
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['pricelist:read']
]);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Promo;
use App\Repository\PromoRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/promo')]
final class PromoController extends AbstractController
{
private const READ_GROUPS = ['promo:read'];
private const WRITE_GROUPS = ['promo:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Акции')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'perPage', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', description: 'Если не передан — фильтр active=true (как в старом API).', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'promo_list', methods: ['GET'])]
public function list(Request $request, PromoRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request, true));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/{id}', name: 'promo_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Promo $promo): JsonResponse
{
return $this->crud->read($promo, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Promo::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'promo_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, Promo::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: Promo::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'promo_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, Promo $promo): JsonResponse
{
return $this->crud->update($request, $promo, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'promo_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Promo $promo): JsonResponse
{
return $this->crud->delete($promo);
}
}
+222
View File
@@ -0,0 +1,222 @@
<?php
namespace App\Controller;
use App\Dto\ReviewInputDto;
use App\Entity\Review;
use App\Entity\Specialist;
use App\Entity\User;
use App\Repository\ReviewRepository;
use App\Repository\SpecialistRepository;
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Attributes as OA;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
#[Route('/review')]
final class ReviewController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer,
) {
}
#[OA\Tag(name: 'Отзывы')]
#[Route('/list', name: 'reviews_list', methods: ['GET'])]
public function list(Request $request, ReviewRepository $repository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$qb = $repository->createFilteredQueryBuilder(
$request->query->all()
);
$adapter = new QueryAdapter($qb);
$pagerfanta = new Pagerfanta($adapter);
$pagerfanta->setMaxPerPage($perPage);
$pagerfanta->setCurrentPage($page);
$data = $pagerfanta->getCurrentPageResults();
$response = [
'data' => $data,
'pagination' => [
'total' => $pagerfanta->getNbResults(),
'count' => count($data),
'per_page' => $pagerfanta->getMaxPerPage(),
'current_page' => $pagerfanta->getCurrentPage(),
'total_pages' => $pagerfanta->getNbPages(),
'has_previous_page' => $pagerfanta->hasPreviousPage(),
'has_next_page' => $pagerfanta->hasNextPage(),
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['review:read', 'to.specialist:read'],
]);
}
#[OA\Tag(name: 'Отзывы')]
#[Route('/{id}', name: 'review_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Review $review): JsonResponse
{
return $this->json($review, Response::HTTP_OK, [], [
'groups' => ['review:read', 'to.specialist:read'],
]);
}
#[OA\Tag(name: 'Отзывы')]
#[IsGranted('ROLE_USER')]
#[Route('/create', name: 'review_create', methods: ['POST'])]
public function create(Request $request, SpecialistRepository $specialistRepository): JsonResponse
{
try {
$dto = $this->serializer->deserialize(
$request->getContent(),
ReviewInputDto::class,
'json'
);
} catch (ExceptionInterface $e) {
return $this->json([
'error' => 'Ошибка десериализации',
'message' => $e->getMessage(),
], Response::HTTP_BAD_REQUEST);
}
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$specialist = $specialistRepository->find((int) $dto->specialistId);
if ($specialist === null) {
return $this->json(['error' => 'Специалист не найден'], Response::HTTP_NOT_FOUND);
}
$user = $this->getUser();
if (!$user instanceof User) {
return $this->json(['error' => 'Требуется авторизация'], Response::HTTP_UNAUTHORIZED);
}
$review = new Review();
$this->applyInput(
$review,
$dto,
$specialist,
active: false,
externalIdFromAuthenticatedUser: $user->getId(),
isNewReview: true,
);
$this->em->persist($review);
try {
$this->em->flush();
} catch (\Throwable $e) {
return $this->json([
'error' => 'Не удалось сохранить отзыв',
'message' => $this->getParameter('kernel.debug') ? $e->getMessage() : 'Внутренняя ошибка сервера',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->json($review, Response::HTTP_CREATED, [], [
'groups' => ['review:read', 'to.specialist:read'],
]);
}
#[OA\Tag(name: 'Отзывы')]
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'review_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, Review $review, SpecialistRepository $specialistRepository): JsonResponse
{
try {
$dto = $this->serializer->deserialize(
$request->getContent(),
ReviewInputDto::class,
'json'
);
} catch (ExceptionInterface $e) {
return $this->json([
'error' => 'Ошибка десериализации',
'message' => $e->getMessage(),
], Response::HTTP_BAD_REQUEST);
}
$errors = $this->validator->validate($dto, null, ['Default', 'review_update']);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$specialist = $specialistRepository->find((int) $dto->specialistId);
if ($specialist === null) {
return $this->json(['error' => 'Специалист не найден'], Response::HTTP_NOT_FOUND);
}
$this->applyInput($review, $dto, $specialist, active: (bool) $dto->active, isNewReview: false);
try {
$this->em->flush();
} catch (\Throwable $e) {
return $this->json([
'error' => 'Не удалось сохранить отзыв',
'message' => $this->getParameter('kernel.debug') ? $e->getMessage() : 'Внутренняя ошибка сервера',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->json($review, Response::HTTP_OK, [], [
'groups' => ['review:read', 'to.specialist:read'],
]);
}
#[OA\Tag(name: 'Отзывы')]
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'review_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Review $review): JsonResponse
{
$this->em->remove($review);
$this->em->flush();
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
}
/**
* Doctrine DBAL DateType ожидает {@see \DateTime}, не {@see \DateTimeImmutable}.
*/
private function toDoctrineDate(\DateTimeInterface $date): \DateTime
{
return $date instanceof \DateTime ? clone $date : \DateTime::createFromInterface($date);
}
private function applyInput(
Review $review,
ReviewInputDto $dto,
Specialist $specialist,
bool $active,
?int $externalIdFromAuthenticatedUser = null,
bool $isNewReview = false,
): void {
$review
->setSpecialist($specialist)
->setActive($active)
->setMessage((string) $dto->message)
->setAuthor((string) $dto->author)
->setRating((float) $dto->rating)
->setSource($dto->source);
if ($isNewReview) {
$review->setDateCreate($this->toDoctrineDate(new \DateTimeImmutable('today')));
if ($externalIdFromAuthenticatedUser !== null) {
$review->setExternalId($externalIdFromAuthenticatedUser);
}
}
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Service\Client\Interfaces\SmartCaptchaClientServiceInterface;
use App\Service\Mail\SendMailConfig;
use App\Service\Mail\SendMailService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Validator\Constraints\Email as EmailConstraint;
use Symfony\Component\Validator\Validator\ValidatorInterface;
final class ServiceController extends AbstractController
{
public function __construct(
private SmartCaptchaClientServiceInterface $smartCaptha,
private SendMailService $sendMailService,
private ValidatorInterface $validator,
private SendMailConfig $sendmailConfig
) { }
#[Route('/service/sendmail', name: 'service_sendmail', methods: ['GET'])]
public function sendmail(Request $request): JsonResponse
{
$accessToken = $this->sendmailConfig->getAccessToken();
if ($accessToken === '') {
return $this->json(['error' => 'Токен доступа не настроен (MAILER_ACCESS_TOKEN)'], 503);
}
$token = $request->query->get('token', '');
if ($token === '' || !hash_equals($accessToken, $token)) {
return $this->json(['error' => 'Неверный или отсутствующий токен доступа'], 403);
}
$mailto = $request->query->get('mailto', '');
$subject = $request->query->get('subject', '');
$message = $request->query->get('message', '');
if ($mailto === '') {
return $this->json(['error' => 'Параметр mailto обязателен'], 400);
}
$emailViolations = $this->validator->validate($mailto, [new EmailConstraint()]);
if (count($emailViolations) > 0) {
return $this->json(['error' => 'Некорректный email в параметре mailto'], 400);
}
if ($subject === '') {
return $this->json(['error' => 'Параметр subject обязателен'], 400);
}
if ($message === '') {
return $this->json(['error' => 'Параметр message обязателен'], 400);
}
try {
$this->sendMailService->send($mailto, $subject, $message);
return $this->json(['success' => true, 'message' => 'Письмо отправлено']);
} catch (\Throwable $e) {
return $this->json([
'error' => 'Ошибка при отправке письма',
'message' => $e->getMessage()
], 500);
}
}
#[Route('/smart-captcha', name: 'service_smart_captcha', methods: ['POST'])]
public function smartCaptcha(Request $request): JsonResponse
{
$response = $this->smartCaptha->validate(
$request->request->get('token', ''),
$request->getClientIp()
);
return $this->json($response);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace App\Controller;
use App\Dto\Content\ContentFilterDto;
use App\Entity\SiteService;
use App\Repository\SiteServiceRepository;
use App\Service\Crud\CrudResponder;
use App\Service\Pagination\Paginator;
use Nelmio\ApiDocBundle\Attribute\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/site-services')]
final class SiteServiceController extends AbstractController
{
private const READ_GROUPS = ['site_service:read'];
private const WRITE_GROUPS = ['site_service:write'];
public function __construct(
private readonly CrudResponder $crud,
private readonly Paginator $paginator,
) {
}
#[OA\Tag(name: 'Услуги')]
#[OA\Parameter(name: 'page', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'perPage', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'regionId', in: 'query', schema: new OA\Schema(type: 'integer'))]
#[OA\Parameter(name: 'active', description: 'Если не передан — фильтр active=true (как в старом API).', in: 'query', schema: new OA\Schema(type: 'boolean'))]
#[OA\Parameter(name: 'search', in: 'query', schema: new OA\Schema(type: 'string'))]
#[Route('/list', name: 'site_service_list', methods: ['GET'])]
public function list(Request $request, SiteServiceRepository $repository): JsonResponse
{
$qb = $repository->createFilteredQueryBuilder(ContentFilterDto::fromRequest($request, true));
return $this->json($this->paginator->paginate($qb, $request), Response::HTTP_OK, [], [
'groups' => self::READ_GROUPS,
]);
}
#[Route('/{id}', name: 'site_service_show', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(SiteService $siteService): JsonResponse
{
return $this->crud->read($siteService, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: SiteService::class, groups: self::WRITE_GROUPS)))]
#[Route('/create', name: 'site_service_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
return $this->crud->create($request, SiteService::class, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[OA\RequestBody(content: new OA\JsonContent(ref: new Model(type: SiteService::class, groups: self::WRITE_GROUPS)))]
#[Route('/{id}', name: 'site_service_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Request $request, SiteService $siteService): JsonResponse
{
return $this->crud->update($request, $siteService, self::WRITE_GROUPS, self::READ_GROUPS);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'site_service_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(SiteService $siteService): JsonResponse
{
return $this->crud->delete($siteService);
}
}
+360
View File
@@ -0,0 +1,360 @@
<?php
namespace App\Controller;
use App\Dto\SpecialistUpdateDto;
use App\Dto\SpecialistFilterDto;
use App\Dto\FileUploadDto;
use App\Entity\Specialist;
use App\Dto\ScheduleDto;
use App\Service\Image\Interfaces\ImageServiceInterface;
use App\Service\Specialist\Interfaces\SpecialistServiceInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use App\Service\FileUploader\Interfaces\FileUploaderServiceInterface;
use Exception;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Repository\SpecialistRepository;
use Doctrine\ORM\Tools\Pagination\Paginator;
use OpenApi\Attributes as OA;
#[Route('/specialist')]
final class SpecialistController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ImageServiceInterface $imageService,
private ValidatorInterface $validator,
private SpecialistServiceInterface $specialistService,
private SerializerInterface $serializer
) { }
#[IsGranted('ROLE_ADMIN')]
#[Route('/create', name: 'specialist_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
try {
$specialist = $this->serializer->deserialize(
$request->getContent(),
Specialist::class,
'json',
['groups' => ['specialist:write']],
);
$errors = $this->validator->validate($specialist);
if (count($errors) > 0) {
return $this->json($errors, 400);
}
$this->em->persist($specialist);
$this->em->flush();
$data = $this->serializer->serialize($specialist, 'json', [
'groups' => ['specialist:detail', 'from.specialist:read'],
'image_size' => 'large'
]);
return new JsonResponse(
$data,
Response::HTTP_OK,
['Content-Type' => 'application/json'],
true
);
} catch (ExceptionInterface $e) {
return new JsonResponse([
'error' => 'Ошибка десериализации',
'message' => $e->getMessage()
], Response::HTTP_BAD_REQUEST);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'specialist_update', methods: ['PUT'], requirements: ['id' => '\d+'])]
public function update(Specialist $specialist, Request $request): JsonResponse
{
try {
$this->serializer->deserialize(
$request->getContent(),
Specialist::class,
'json',
[
'groups' => ['specialist:write'],
'object_to_populate' => $specialist,
]
);
$errors = $this->validator->validate($specialist);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
$data = $this->serializer->serialize($specialist, 'json', [
'groups' => ['specialist:detail', 'from.specialist:read'],
'image_size' => 'large'
]);
return new JsonResponse(
$data,
Response::HTTP_OK,
['Content-Type' => 'application/json'],
true
);
} catch (ExceptionInterface $e) {
return new JsonResponse([
'error' => 'Ошибка десериализации',
'message' => $e->getMessage()
], Response::HTTP_BAD_REQUEST);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/{id}', name: 'specialist_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])]
public function delete(Specialist $specialist, FileUploaderServiceInterface $fileUploader): JsonResponse
{
try {
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $specialist->getPreviewPicture());
$this->em->remove($specialist);
$this->em->flush();
return new JsonResponse(null, 204);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], 500);
}
}
#[OA\Tag(name: 'Список врачей')]
#[OA\Parameter(
name: 'page',
in: 'query',
description: 'номер станицы',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'perPage',
in: 'query',
description: 'количество записей',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'filial',
in: 'query',
description: 'id филиала',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'department',
in: 'query',
description: 'id отделения',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'search',
in: 'query',
description: 'поиск по имени',
schema: new OA\Schema(type: 'string')
)]
#[Route('/list', name: 'specialist_list', methods: ['GET'])]
public function list(
Request $request,
SpecialistRepository $repository,
): JsonResponse {
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$filter = SpecialistFilterDto::fromRequest($request);
$total = $this->specialistService->getFilteredCount($filter);
$qb = $repository->createFilteredQueryBuilder($filter->toArray());
$qb->setFirstResult(($page - 1) * $perPage)
->setMaxResults($perPage);
$paginator = new Paginator($qb->getQuery(), true);
$data = iterator_to_array($paginator->getIterator(), false);
$totalPages = ceil($total / $perPage);
$response = [
'data' => $data,
'pagination' => [
'total' => $total,
'count' => count($data),
'per_page' => $perPage,
'current_page' => $page,
'total_pages' => $totalPages,
'has_previous_page' => $page > 1,
'has_next_page' => $page < $totalPages,
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['specialist:read', 'from.specialist:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/post', name: 'specialist_post', methods: ['GET'])]
public function specialistPost(SpecialistRepository $specialist): JsonResponse
{
return $this->json($specialist->post());
}
#[Route('/picture/{id}', name: 'specialist_picture', methods: ['GET'])]
public function specialistPicture(Specialist $specialist, Request $request): Response
{
$height = min($request->query->getInt('height', 300), 800);
$width = min($request->query->getInt('width', 300), 600);
$uploadDir = $this->getParameter('upload_directory');
$file = $this->specialistService->getLoadPicture($specialist->getId());
return $this->imageService->getPicture($uploadDir . '/' .$file, $width, $height);
}
#[OA\Tag(name: 'Расписание врачей')]
#[OA\Parameter(
name: 'st',
in: 'query',
description: 'Начальная дата (Ymd)',
required:true,
schema: new OA\Schema(type: 'integer', format:'Ymd')
)]
#[OA\Parameter(
name: 'en',
in: 'query',
description: 'Конечна дата (Ymd)',
required:true,
schema: new OA\Schema(type: 'integer', format:'Y-m-d')
)]
#[OA\Parameter(
name: 'dcode',
in: 'query',
description: 'ID врача',
required:true,
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'filial',
in: 'query',
description: 'ID филиала',
required:true,
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'onlineMode',
in: 'query',
description: 'Онлайн расписание',
required:true,
schema: new OA\Schema(type: 'boolean')
)]
#[Route('/schedule', name: 'specialist_schedule', methods: ['GET'])]
public function specialistSchedule(
Request $request,
ScheduleDto $dto,
): JsonResponse {
$dto->st = $request->query->get('st');
$dto->en = $request->query->get('en');
$dto->dcode = $request->query->get('dcode');
$dto->onlineMode = $request->query->getBoolean('onlineMode')? 1: 0;
$dto->filial = $request->query->get('filial');
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json(['errors' => (string) $errors], 400);
}
$schedule = $this->specialistService->getSchedule($dto);
return $this->json($schedule, 200, []);
}
#[IsGranted('ROLE_ADMIN')]
#[Route('/picture/{id}', name: 'specialist_upload_picture', methods: ['POST'], requirements: ['id' => '\d+'])]
public function specialistUpload(
Specialist $specialist,
Request $request,
FileUploadDto $dto,
FileUploaderServiceInterface $fileUploader
): JsonResponse {
try {
$uploadedFile = $request->files->get('previewPicture');
if (!$uploadedFile instanceof UploadedFile) {
return $this->json(['error' => 'File not uploaded'], 400);
}
$dto->file = $uploadedFile;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json($errors, 400);
}
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $specialist->getPreviewPicture());
$fileUploader->setTargetDirectory('specialist');
$fileName = $fileUploader->upload($uploadedFile);
$specialist->setPreviewPicture('specialist/'. $fileName);
$this->em->persist($specialist);
$this->em->flush();
return $this->json($specialist, 200, [], [
'groups' => ['specialist:detail', 'from.specialist:read']
]);
} catch (Exception $e) {
return $this->json([
'error' => 'File upload failed',
'message' => $e->getMessage()
], 500);
}
}
#[OA\Tag(name: 'Детальная информация врача')]
#[OA\Parameter(
name: 'id',
in: 'path',
description: 'id врача',
schema: new OA\Schema(type: 'integer')
)]
#[Route('/{id}', name: 'specialist_detal', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Specialist $specialist): JsonResponse
{
return $this->json($specialist, Response::HTTP_OK, [], [
'groups' => ['specialist:detail', 'from.specialist:read']
]);
}
#[Route('/by/{identifier}', name: 'specialist_detal_by', methods: ['GET'], requirements: ['identifier' => '[a-zA-Z0-9\-_]+'])]
public function showBy(string $identifier, Request $request): JsonResponse
{
$regionId = $request->query->getInt('regionId');
$specialist = $this->specialistService->getSpecialist($identifier, $regionId);
if (!$specialist) {
return $this->json(['error' => 'not found'], Response::HTTP_NOT_FOUND);
}
return $this->json($specialist, Response::HTTP_OK, [], [
'groups' => ['specialist:detail', 'from.specialist:read']
]);
}
}
@@ -0,0 +1,184 @@
<?php
namespace App\Controller;
use App\Entity\Specialist;
use App\Entity\SpecialistDcodeDescription;
use App\Repository\SpecialistDcodeDescriptionRepository;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
final class SpecialistDcodeDescriptionController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) {
}
#[Route(
path: '/specialist-dcode-description/list',
name: 'specialist_dcode_description_list',
methods: ['GET']
)]
public function list(Request $request, SpecialistDcodeDescriptionRepository $repository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$qb = $repository->createFilteredQueryBuilder($request->query->all());
$pager = new Pagerfanta(new QueryAdapter($qb));
$pager->setMaxPerPage($perPage);
$pager->setCurrentPage($page);
$items = iterator_to_array($pager->getCurrentPageResults());
$data = array_map(
fn (SpecialistDcodeDescription $item) => $this->normalizeWithSpeciality($item),
$items
);
$response = [
'data' => $data,
'pagination' => [
'total' => $pager->getNbResults(),
'count' => count($data),
'per_page' => $pager->getMaxPerPage(),
'current_page' => $pager->getCurrentPage(),
'total_pages' => $pager->getNbPages(),
'has_previous_page' => $pager->hasPreviousPage(),
'has_next_page' => $pager->hasNextPage(),
],
];
return $this->json($response, Response::HTTP_OK);
}
#[Route(
'/specialist-dcode-description/{id}',
name: 'specialist_dcode_description_detail',
methods: ['GET'],
requirements: ['id' => '\d+']
)]
public function show(SpecialistDcodeDescription $specialistDcodeDescription): JsonResponse
{
return $this->json($this->normalizeWithSpeciality($specialistDcodeDescription), Response::HTTP_OK);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist/{specialistId}/specialist-dcode-description/create',
name: 'specialist_dcode_description_create',
methods: ['POST'],
requirements: ['specialistId' => '\d+']
)]
public function create(int $specialistId, Request $request): JsonResponse
{
$specialist = $this->em->getRepository(Specialist::class)->find($specialistId);
if (!$specialist) {
return $this->json(['error' => 'Специалист не найден'], Response::HTTP_NOT_FOUND);
}
$entity = $this->serializer->deserialize(
$request->getContent(),
SpecialistDcodeDescription::class,
'json',
['groups' => ['specialist.dcode.description:write']]
);
$entity->setSpecialist($specialist);
$errors = $this->validator->validate($entity);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($entity);
$this->em->flush();
return $this->json($this->normalizeWithSpeciality($entity), Response::HTTP_CREATED);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist/{specialistId}/specialist-dcode-description/{id}',
name: 'specialist_dcode_description_update',
methods: ['PUT'],
requirements: ['specialistId' => '\d+', 'id' => '\d+']
)]
public function update(Request $request, int $specialistId, SpecialistDcodeDescription $entity): JsonResponse
{
try {
$specialist = $this->em->getRepository(Specialist::class)->find($specialistId);
if (!$specialist) {
return $this->json(['error' => 'Специалист не найден'], Response::HTTP_NOT_FOUND);
}
$this->serializer->deserialize(
$request->getContent(),
SpecialistDcodeDescription::class,
'json',
[
'groups' => ['specialist.dcode.description:write'],
'object_to_populate' => $entity,
]
);
$entity->setSpecialist($specialist);
$errors = $this->validator->validate($entity);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($this->normalizeWithSpeciality($entity), Response::HTTP_OK);
} catch (Exception $e) {
return $this->json([
'error' => 'Произошла ошибка при обновлении',
'message' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist-dcode-description/{id}',
name: 'specialist_dcode_description_delete',
methods: ['DELETE'],
requirements: ['id' => '\d+']
)]
public function delete(SpecialistDcodeDescription $entity): JsonResponse
{
try {
$this->em->remove($entity);
$this->em->flush();
return new JsonResponse(null, Response::HTTP_NO_CONTENT);
} catch (Exception $e) {
return $this->json([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
private function normalizeWithSpeciality(SpecialistDcodeDescription $entity): array
{
$normalized = json_decode($this->serializer->serialize($entity, 'json', [
'groups' => ['specialist.dcode.description:read', 'to.specialist:read'],
]), true);
$normalized['speciality'] = $entity->getSpecialist()?->getPost();
return $normalized;
}
}
+223
View File
@@ -0,0 +1,223 @@
<?php
namespace App\Controller;
use App\Dto\FileUploadDto;
use App\Entity\SpecialistDocs;
use App\Entity\Specialist;
use App\Repository\SpecialistDocsRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\FileUploader\Interfaces\FileUploaderServiceInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Service\Image\Interfaces\ImageServiceInterface;
use Exception;
final class SpecialistDocsController extends AbstractController
{
public function __construct(
private ImageServiceInterface $imageService,
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) { }
#[Route(
path: '/specialist-docs/list',
name: 'specialist_docs_list',
methods: ['GET']
)]
public function empty(SpecialistDocsRepository $repository): JsonResponse
{
return $this->json([
'data' => $repository->findAll()
], Response::HTTP_OK, [], [
'groups' => ['specialist.docs:read', 'to.specialist:read']
]);
}
#[Route('specialist-docs/{id}', name: 'specialist_docs_detal', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(SpecialistDocs $specialistDocs): JsonResponse
{
return $this->json(
$specialistDocs,
Response::HTTP_OK, [], [
'groups' => ['specialist.docs:read', 'to.specialist:read']
]);
}
#[Route('/specialist-docs/picture/{id}', name: 'specialist_docs_picture', methods: ['GET'])]
public function specialistPicture(SpecialistDocs $specialistDocs, Request $request): Response
{
$height = min($request->query->getInt('height', 300), 800);
$width = min($request->query->getInt('width', 300), 600);
$uploadDir = $this->getParameter('upload_directory');
$file = $specialistDocs->getPicture();
return $this->imageService->getPicture($uploadDir . '/' .$file, $width, $height);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist/{id}/specialist-docs/create',
name: 'specialist_docs_create',
methods: ['POST'],
requirements: ['id' => '\d+']
)]
public function create(Specialist $specialist, Request $request): JsonResponse
{
$specialistDocs = $this->serializer->deserialize(
$request->getContent(),
SpecialistDocs::class,
'json',
['groups' => ['specialist.docs:write']]
);
$specialistDocs->setSpecialist($specialist);
$errors = $this->validator->validate($specialistDocs);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($specialistDocs);
$this->em->flush();
return $this->json($specialistDocs, Response::HTTP_CREATED, [], [
'groups' => ['specialist.docs:read', 'to.specialist:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist/{specialistId}/specialist-docs/{id}',
name: 'specialist_docs_update',
methods: ['PUT'],
requirements: ['id' => '\d+', 'specialistId' => '\d+']
)]
public function update(Request $request, int $specialistId, SpecialistDocs $specialistDocs): JsonResponse
{
try {
$this->serializer->deserialize(
$request->getContent(),
SpecialistDocs::class,
'json',
[
'groups' => ['specialist.docs:write'],
'object_to_populate' => $specialistDocs
]
);
$specialist = $this->em->getRepository(Specialist::class)->find($specialistId);
if (!$specialist) {
return new JsonResponse([
'error' => 'Специалист не найден'
], Response::HTTP_NOT_FOUND);
}
$specialistDocs->setSpecialist($specialist);
$errors = $this->validator->validate($specialistDocs);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($specialistDocs, Response::HTTP_OK, [], [
'groups' => ['specialist.docs:read', 'to.specialist:read']
]);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist-docs/{id}',
name: 'specialist_docs_delete',
methods: ['DELETE'],
requirements: ['id' => '\d+']
)]
public function delete(SpecialistDocs $specialistDocs, FileUploaderServiceInterface $fileUploader): JsonResponse
{
try {
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $specialistDocs->getPicture());
$this->em->remove($specialistDocs);
$this->em->flush();
return new JsonResponse(null, 204);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], 500);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/specialist-docs/picture/{id}',
name: 'specialist_docs_upload_picture',
methods: ['POST'],
requirements: ['id' => '\d+']
)]
public function uploadPicture(
SpecialistDocs $specialistDocs,
Request $request,
FileUploadDto $dto,
FileUploaderServiceInterface $fileUploader
): JsonResponse {
try {
$uploadedFile = $request->files->get('picture');
if (!$uploadedFile instanceof UploadedFile) {
return $this->json(['error' => 'File not uploaded'], 400);
}
$dto->file = $uploadedFile;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json($errors, 400);
}
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $specialistDocs->getPicture());
$fileUploader->setTargetDirectory($specialistDocs->getType());
$fileName = $fileUploader->upload($uploadedFile);
$specialistDocs->setPicture($specialistDocs->getType() . '/' . $fileName);
$this->em->persist($specialistDocs);
$this->em->flush();
return $this->json($specialistDocs, 200, [], [
'groups' => ['specialist.docs:read', 'to.specialist:read']
]);
} catch (Exception $e) {
return $this->json([
'error' => 'File upload failed',
'message' => $e->getMessage()
], 500);
}
}
}
+266
View File
@@ -0,0 +1,266 @@
<?php
namespace App\Controller;
use App\Dto\FileUploadDto;
use App\Entity\Stock;
use App\Entity\Specialist;
use App\Repository\StockRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\FileUploader\Interfaces\FileUploaderServiceInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Service\Image\Interfaces\ImageServiceInterface;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use Exception;
final class StockController extends AbstractController
{
public function __construct(
private ImageServiceInterface $imageService,
private EntityManagerInterface $em,
private ValidatorInterface $validator,
private SerializerInterface $serializer
) { }
#[Route(
path: '/stock/list',
name: 'stock_list',
methods: ['GET']
)]
public function list(Request $request, StockRepository $repository): JsonResponse
{
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$qb = $repository->createFilteredQueryBuilder(
$request->query->all()
);
$pager = new Pagerfanta(new QueryAdapter($qb));
$pager->setMaxPerPage($perPage);
$pager->setCurrentPage($page);
$data = $pager->getCurrentPageResults();
$response = [
'data' => $data,
'pagination' => [
'total' => $pager->getNbResults(),
'count' => count($data),
'per_page' => $pager->getMaxPerPage(),
'current_page' => $pager->getCurrentPage(),
'total_pages' => $pager->getNbPages(),
'has_previous_page' => $pager->hasPreviousPage(),
'has_next_page' => $pager->hasNextPage(),
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
}
#[Route('stock/{id}', name: 'stock_detal', methods: ['GET'], requirements: ['id' => '\d+'])]
public function show(Stock $stock): JsonResponse
{
return $this->json($stock, Response::HTTP_OK, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
}
#[Route('/stock/picture/{id}', name: 'stock_picture', methods: ['GET'])]
public function picture(Stock $stock, Request $request): Response
{
$height = min($request->query->getInt('height', 300), 800);
$width = min($request->query->getInt('width', 300), 600);
$uploadDir = $this->getParameter('upload_directory');
$file = $stock->getPicture();
return $this->imageService->getPicture($uploadDir . '/' .$file, $width, $height);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: 'stock/{id}/specialist/{specialistId}',
name: 'stock_specialist_update',
methods: ['DELETE', 'PUT'],
requirements: ['id' => '\d+']
)]
public function updateSpecialist(Request $request, int $specialistId, Stock $stock): JsonResponse
{
$specialist = $this->em->getRepository(Specialist::class)->find($specialistId);
if (!$specialist) {
return new JsonResponse([
'error' => 'Специалист не найден'
], Response::HTTP_NOT_FOUND);
}
$request->getMethod() === 'PUT'
? $stock->addSpecialist($specialist)
: $stock->removeSpecialist($specialist);
$errors = $this->validator->validate($stock);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($stock, Response::HTTP_OK, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/stock/create',
name: 'stock_create',
methods: ['POST']
)]
public function create(Request $request): JsonResponse
{
$stock = $this->serializer->deserialize(
$request->getContent(),
Stock::class,
'json',
['groups' => ['stock:write']]
);
$errors = $this->validator->validate($stock);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->persist($stock);
$this->em->flush();
return $this->json($stock, Response::HTTP_CREATED, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/stock/{id}',
name: 'stock_update',
methods: ['PUT'],
requirements: ['id' => '\d+']
)]
public function update(Request $request, Stock $stock): JsonResponse
{
try {
$this->serializer->deserialize(
$request->getContent(),
Stock::class,
'json',
[
'groups' => ['stock:write'],
'object_to_populate' => $stock
]
);
$errors = $this->validator->validate($stock);
if (count($errors) > 0) {
return $this->json($errors, Response::HTTP_BAD_REQUEST);
}
$this->em->flush();
return $this->json($stock, Response::HTTP_OK, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/stock/{id}',
name: 'stock_delete',
methods: ['DELETE'],
requirements: ['id' => '\d+']
)]
public function delete(Stock $stock, FileUploaderServiceInterface $fileUploader): JsonResponse
{
try {
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $stock->getPicture());
$this->em->remove($stock);
$this->em->flush();
return new JsonResponse(null, 204);
} catch (Exception $e) {
return new JsonResponse([
'error' => 'Произошла ошибка при удалении',
'message' => $e->getMessage()
], 500);
}
}
#[IsGranted('ROLE_ADMIN')]
#[Route(
path: '/stock/picture/{id}',
name: 'stock_upload_picture',
methods: ['POST'],
requirements: ['id' => '\d+']
)]
public function uploadPicture(
Stock $stock,
Request $request,
FileUploadDto $dto,
FileUploaderServiceInterface $fileUploader
): JsonResponse {
try {
$uploadedFile = $request->files->get('picture');
if (!$uploadedFile instanceof UploadedFile) {
return $this->json(['error' => 'File not uploaded'], 400);
}
$dto->file = $uploadedFile;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json($errors, 400);
}
$fileUploader->remove($fileUploader->getTargetDirectory() .'/'. $stock->getPicture());
$fileUploader->setTargetDirectory('stock');
$fileName = $fileUploader->upload($uploadedFile);
$stock->setPicture('stock/' . $fileName);
$this->em->persist($stock);
$this->em->flush();
return $this->json($stock, 200, [], [
'groups' => ['stock:read', 'to.specialist:read']
]);
} catch (Exception $e) {
return $this->json([
'error' => 'File upload failed',
'message' => $e->getMessage()
], 500);
}
}
}
+286
View File
@@ -0,0 +1,286 @@
<?php
namespace App\Controller;
use App\Dto\RegionDto;
use App\Dto\UserAuthDto;
use App\Dto\UserLoginDto;
use App\Dto\UserUidAuthDto;
use App\Service\DecoderJWT\Interfaces\JWTDecoderServiceInterface;
use App\Service\User\Interfaces\AuthenticationServiceInterface;
use App\Service\User\Interfaces\UserProfileServiceInterface;
use App\Service\User\Interfaces\RegistrationServiceInterface;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpFoundation\Request;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use OpenApi\Attributes as OA;
use Doctrine\ORM\EntityManagerInterface;
#[Route('/user')]
final class UserController extends AbstractController
{
public function __construct(
private AuthenticationServiceInterface $auth,
private JWTTokenManagerInterface $jwtManager,
private ValidatorInterface $validator,
private UserRepository $userRepository,
private EntityManagerInterface $em
) { }
#[Route('/logout', name:'user_logout', methods:['GET'])]
public function logout(): JsonResponse
{
return $this->json(['successful' => true]);
}
#[Route('/login', name: 'user_login', methods: ['POST'])]
#[OA\Tag(name: 'Авторизация пользователя')]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'username', type: 'string', description: 'Email пользователя'),
new OA\Property(property: 'password', type: 'string', description: 'Пароль пользователя')
],
required: ['username', 'password']
)
)]
#[OA\Response(
response: 200,
description: 'Успешная авторизация',
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'successful', type: 'boolean'),
new OA\Property(property: 'token', type: 'string', description: 'JWT токен'),
new OA\Property(property: 'user', type: 'object', description: 'Данные пользователя')
]
)
)]
#[OA\Response(
response: 400,
description: 'Ошибка валидации или неверные учетные данные'
)]
public function login(UserLoginDto $dto, Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
if (!$data || !isset($data['username']) || !isset($data['password'])) {
return new JsonResponse(['message' => 'Missing credentials'], 400);
}
$dto->email = $data['username'] ?? null;
$dto->password = $data['password'] ?? null;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json([
'successful' => false,
'errors' => (string) $errors
], 400);
}
$userData = $this->auth->jsonAuth($dto);
if ($userData['isPasswordValid'] === false || $userData['user'] === null) {
return $this->json([
'successful' => false,
'message' => 'Не правильное имя пользователя или пароль'
], 400);
}
// Обновляем дату и время авторизации
$userData['user']->updateLoggedIn();
$this->em->flush();
$user = $userData['user']->toArray();
return new JsonResponse([
'successful' => true,
'token' => $this->jwtManager->create($userData['user']),
'user' => $user
], 200);
}
#[IsGranted('ROLE_USER')]
#[Route('/', name: 'user_current', methods: ['GET'])]
public function index(JWTDecoderServiceInterface $jwtDecoderService): JsonResponse
{
$user = $jwtDecoderService->getUser();
$user = $user->toArray();
return $this->json([
'user' => $user,
'successful' => true,
]);
}
#[IsGranted('ROLE_USER')]
#[Route('/change-region', name: 'user_change_region', methods: ['PUT'])]
public function changeRegion(
UserProfileServiceInterface $userService,
RegionDto $dto,
Request $request
): JsonResponse {
$data = json_decode($request->getContent(), true);
$dto->regionId = $data['regionId'];
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json(['errors' => (string) $errors], 400);
}
$user = $userService->updateRegion($dto);
$user = $user->toArray();
return $this->json([
'user' => $user,
'successful' => true,
]);
}
#[Route('/auth', name: 'user_auth', methods: ['POST'])]
public function auth(
RegistrationServiceInterface $registration,
UserAuthDto $dto,
Request $request
): JsonResponse {
$data = json_decode($request->getContent(), true);
$dto->uid = $data['uid'] ?? null;
$dto->regionId = $data['regionId'] ?? null;
$dto->email = $data['email'] ?? null;
$dto->password = $data['password'] ?? null;
$dto->birthDate = $data['bdate'] ?? null;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json([
'successful' => false,
'errors' => (string) $errors
], 400);
}
$userData = $this->auth->jwtAuth($dto);
if ($userData['isPasswordValid'] === false) {
return $this->json([
'successful' => false,
'message' => 'Не правильное имя пользователя или пароль'
], 400);
}
if ($userData['user'] === null) {
$userData['user'] = $registration->create($dto);
}
// Обновляем дату и время авторизации
$userData['user']->updateLoggedIn();
$this->em->flush();
$user = $userData['user']->toArray();
return $this->json([
'successful' => true,
'token' => $this->jwtManager->create($userData['user']),
'user' => $user
]);
}
#[Route('/auth-by-pcode', name: 'user_auth_by_pcode', methods: ['POST'])]
#[OA\Tag(name: 'Авторизация пользователя')]
#[OA\RequestBody(
required: true,
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'pcode', type: 'integer', description: 'Pcode пользователя (UID)'),
new OA\Property(property: 'birthDate', type: 'string', description: 'Дата рождения в формате Ymd (например, 19900101) или Y-m-d (например, 1990-01-01)')
],
required: ['pcode', 'birthDate']
)
)]
#[OA\Response(
response: 200,
description: 'Успешная авторизация. Если пользователь не найден, он будет создан автоматически',
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'successful', type: 'boolean'),
new OA\Property(property: 'token', type: 'string', description: 'JWT токен'),
new OA\Property(property: 'user', type: 'object', description: 'Данные пользователя')
]
)
)]
#[OA\Response(
response: 400,
description: 'Ошибка валидации'
)]
#[OA\Response(
response: 500,
description: 'Ошибка при создании пользователя'
)]
public function authByPcode(
RegistrationServiceInterface $registration,
UserUidAuthDto $dto,
Request $request
): JsonResponse {
$data = json_decode($request->getContent(), true);
$dto->uid = $data['pcode'] ?? null;
$dto->birthDate = $data['birthDate'] ?? $data['bdate'] ?? null;
$errors = $this->validator->validate($dto);
if (count($errors) > 0) {
return $this->json([
'successful' => false,
'errors' => (string) $errors
], 400);
}
// Парсим дату рождения
$birthDate = \DateTime::createFromFormat('Ymd', $dto->birthDate);
if (!$birthDate) {
$birthDate = \DateTime::createFromFormat('Y-m-d', $dto->birthDate);
}
if (!$birthDate) {
return $this->json([
'successful' => false,
'errors' => 'Неверный формат даты рождения. Используйте формат Ymd (например, 19900101) или Y-m-d (например, 1990-01-01)'
], 400);
}
// Ищем пользователя по uid и birthDate
$user = $this->userRepository->findOneByUidAndBirthDate($dto->uid, $birthDate);
// Если пользователь не найден, создаем его
if ($user === null) {
try {
$user = $registration->createByUidAndBirthDate($dto);
} catch (\Exception $e) {
return $this->json([
'successful' => false,
'errors' => 'Ошибка при создании пользователя: ' . $e->getMessage()
], 500);
}
}
// Обновляем дату и время авторизации
$user->updateLoggedIn();
$this->em->flush();
$userArray = $user->toArray();
return $this->json([
'successful' => true,
'token' => $this->jwtManager->create($user),
'user' => $userArray
]);
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Controller;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ParameterType;
use DateTimeImmutable;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
final class UsrlogController extends AbstractController
{
public function __construct(
#[Autowire(service: 'doctrine.dbal.cabinet_connection')]
private readonly Connection $cabinetConnection
) {
}
#[IsGranted('ROLE_LOGS')]
#[Route('/usrlog/list', name: 'usrlog_list', methods: ['GET'])]
public function list(Request $request): JsonResponse
{
$limit = min(max(1, $request->query->getInt('limit', 50)), 500);
$offset = max(0, $request->query->getInt('offset', 0));
$usrlogTable = 'public.usrlog';
$date = $request->query->get('date');
$dateFrom = null;
$dateTo = null;
if ($date !== null && $date !== '') {
$dateFrom = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date . ' 00:00:00');
$dateTo = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $date . ' 23:59:59');
if (!$dateFrom || !$dateTo) {
return $this->json([
'error' => 'Invalid date format. Expected Y-m-d.',
], Response::HTTP_BAD_REQUEST);
}
}
$countQb = $this->cabinetConnection->createQueryBuilder()
->select('COUNT(*)')
->from($usrlogTable, 'u');
$listQb = $this->cabinetConnection->createQueryBuilder()
->select('u.id', 'u.pcode', 'u.agent', 'u.client_ip', 'u.method', 'u.created_at')
->from($usrlogTable, 'u')
->orderBy('u.created_at', 'DESC')
->setFirstResult($offset)
->setMaxResults($limit);
if ($dateFrom && $dateTo) {
$countQb
->andWhere('u.created_at BETWEEN :date_from AND :date_to')
->setParameter('date_from', $dateFrom->format('Y-m-d H:i:s'), ParameterType::STRING)
->setParameter('date_to', $dateTo->format('Y-m-d H:i:s'), ParameterType::STRING);
$listQb
->andWhere('u.created_at BETWEEN :date_from AND :date_to')
->setParameter('date_from', $dateFrom->format('Y-m-d H:i:s'), ParameterType::STRING)
->setParameter('date_to', $dateTo->format('Y-m-d H:i:s'), ParameterType::STRING);
}
$total = (int) $countQb->executeQuery()->fetchOne();
$rows = $listQb->executeQuery()->fetchAllAssociative();
$currentPage = $limit > 0 ? (int) floor($offset / $limit) + 1 : 1;
$totalPages = $total > 0 ? (int) ceil($total / $limit) : 0;
return $this->json([
'data' => $rows,
'pagination' => [
'total' => $total,
'count' => count($rows),
'limit' => $limit,
'offset' => $offset,
'current_page' => $currentPage,
'total_pages' => $totalPages,
'has_previous_page' => $offset > 0,
'has_next_page' => ($offset + $limit) < $total,
],
], Response::HTTP_OK);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
namespace App\Controller;
use App\Repository\WebGetDocinfoRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use OpenApi\Attributes as OA;
#[Route('/docinfo')]
final class WebGetDocinfoController extends AbstractController
{
#[Route('/list', name: 'web_get_docinfo_list', methods: ['GET'])]
#[OA\Tag(name: 'WebGetDocinfo')]
#[OA\Parameter(
name: 'page',
in: 'query',
description: 'Номер страницы',
schema: new OA\Schema(type: 'integer', default: 1)
)]
#[OA\Parameter(
name: 'perPage',
in: 'query',
description: 'Количество записей на странице',
schema: new OA\Schema(type: 'integer', default: 100, maximum: 500)
)]
#[OA\Parameter(
name: 'id',
in: 'query',
description: 'Фильтр по id',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'sourceTable',
in: 'query',
description: 'Фильтр по source_table',
schema: new OA\Schema(type: 'string')
)]
#[OA\Parameter(
name: 'filial',
in: 'query',
description: 'Фильтр по filial',
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'search',
in: 'query',
description: 'Поиск по docName и docPost',
schema: new OA\Schema(type: 'string')
)]
public function list(
Request $request,
WebGetDocinfoRepository $repository
): JsonResponse {
$page = $request->query->getInt('page', 1);
$perPage = min($request->query->getInt('perPage', 100), 500);
$filters = [];
if ($request->query->has('id')) {
$filters['id'] = $request->query->getInt('id');
}
if ($request->query->has('sourceTable')) {
$filters['sourceTable'] = $request->query->get('sourceTable');
}
if ($request->query->has('filial')) {
$filters['filial'] = $request->query->getInt('filial');
}
if ($request->query->has('search')) {
$filters['search'] = $request->query->get('search');
}
$qb = $repository->createFilteredQueryBuilder($filters);
// Подсчет общего количества
$countQb = clone $qb;
$countQb->select('COUNT(w.id)');
$total = (int) $countQb->getQuery()->getSingleScalarResult();
// Пагинация
$qb->setFirstResult(($page - 1) * $perPage)
->setMaxResults($perPage);
$data = $qb->getQuery()->getResult();
$totalPages = ceil($total / $perPage);
$response = [
'data' => $data,
'pagination' => [
'total' => $total,
'count' => count($data),
'per_page' => $perPage,
'current_page' => $page,
'total_pages' => $totalPages,
'has_previous_page' => $page > 1,
'has_next_page' => $page < $totalPages,
],
];
return $this->json($response, Response::HTTP_OK, [], [
'groups' => ['web_get_docinfo:read']
]);
}
#[Route('/dms/{filial}/{dcode}', name: 'web_get_docinfo_dms', methods: ['GET'])]
#[OA\Tag(name: 'WebGetDocinfo')]
#[OA\Parameter(
name: 'filial',
in: 'path',
description: 'ID филиала',
required: true,
schema: new OA\Schema(type: 'integer')
)]
#[OA\Parameter(
name: 'dcode',
in: 'path',
description: 'ID записи (dcode)',
required: true,
schema: new OA\Schema(type: 'integer')
)]
#[OA\Response(
response: 200,
description: 'Информация о DMS',
content: new OA\JsonContent(
properties: [
new OA\Property(property: 'dms', type: 'integer', nullable: true, description: 'Информация о DMS из поля accepts_dms')
]
)
)]
#[OA\Response(
response: 404,
description: 'Запись не найдена'
)]
public function getDms(
int $filial,
int $dcode,
WebGetDocinfoRepository $repository
): JsonResponse {
$docinfo = $repository->findByFilialAndId($filial, $dcode);
if (!$docinfo) {
return $this->json(['error' => 'not found'], Response::HTTP_NOT_FOUND);
}
return $this->json([
'dms' => $docinfo->getAcceptsDms()
], Response::HTTP_OK);
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace App\Controller;
use App\Service\XmlFeedGenerator\XmlFeedGeneratorService;
use App\Service\XmlFeedGenerator\XmlFeedGeneratorV1Service;
use App\Service\Specialist\SpecialistService;
use App\Service\Location\LocationService;
use App\Service\PriceList\PriceListService;
use App\Repository\FilialRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
final class XmlFeedController extends AbstractController
{
public function __construct(
private XmlFeedGeneratorService $xmlFeed,
private XmlFeedGeneratorV1Service $xmlFeedv1,
private SpecialistService $specialistService,
private FilialRepository $filialRepository,
private LocationService $locationService,
private PriceListService $priceListService,
) {}
#[Route('/xml/feed', name: 'yandex_feed', methods:['GET'])]
public function generateFeed(Request $request): Response
{
$currentFilial = $this->filialRepository->findOneBy([
'fid' => $request->query->getInt('filial', 0)
]);
if (!$currentFilial) {
return new Response();
}
// Собираем UTM-параметры из запроса
$utmParams = [
'utm_source' => $request->query->get('utm_source'),
'utm_medium' => $request->query->get('utm_medium'),
'utm_campaign' => $request->query->get('utm_campaign'),
'utm_term' => $request->query->get('utm_term'),
'utm_content' => $request->query->get('utm_content'),
];
$xmlContent = $this->xmlFeed->generateFeed($currentFilial, $utmParams);
$response = new Response($xmlContent);
$response->headers->set('Content-Type', 'application/xml; charset=utf-8');
return $response;
}
#[Route('/xml/feed/v1', name: 'yandex_feed_v1', methods:['GET'])]
public function generateFeedV1(Request $request): Response
{
$filialsParam = $request->query->get('filials');
$regionId = $request->query->getInt('regionId', 0);
if ($filialsParam !== null && $filialsParam !== '') {
$allowedFids = array_map('intval', array_filter(explode(',', $filialsParam)));
if ($allowedFids !== []) {
// При явном списке filials — загружаем по fid и active, без привязки к regionId
$filials = $this->filialRepository->findBy(
['fid' => $allowedFids, 'active' => true],
['fid' => 'ASC']
);
$byFid = [];
foreach ($filials as $f) {
$byFid[$f->getFid()] = $f;
}
$filials = [];
foreach ($allowedFids as $fid) {
if (isset($byFid[$fid])) {
$filials[] = $byFid[$fid];
}
}
} else {
$filials = [];
}
} else {
$filials = $this->filialRepository->findBy(
[
'regionId' => $regionId,
'active' => true,
],
['fid' => 'ASC']
);
}
if ($filials === []) {
return new Response();
}
$xmlContent = $this->xmlFeedv1->generateFeed($filials);
$response = new Response($xmlContent);
$response->headers->set('Content-Type', 'application/xml; charset=utf-8');
return $response;
}
}
+101
View File
@@ -0,0 +1,101 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class AnonymousReserveRequestDto
{
#[Assert\NotBlank]
#[Assert\Type('boolean')]
public bool $accept = true;
#[Assert\NotBlank]
#[Assert\Type('string')]
#[Assert\Length(min: 2, max: 100)]
public string $fio;
#[Assert\NotBlank]
#[Assert\Type('string')]
public string $captcha;
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\NotBlank]
#[Assert\Type('string')]
#[Assert\Regex(
pattern: '/^\+7\(\d{3}\)\d{3}-\d{2}-\d{2}$/',
message: 'Телефон должен быть в формате: +7(999)999-99-99'
)]
public string $phone;
#[Assert\NotBlank]
#[Assert\Type('string')]
#[Assert\Regex('/^\d{2}:\d{2}-\d{2}:\d{2}$/')]
public string $time;
#[Assert\NotBlank]
#[Assert\Type('string')]
#[Assert\Regex('/^\d{8}$/')]
public string $workDate;
#[Assert\Type('array')]
public array $services = [];
#[Assert\NotBlank]
#[Assert\Type('integer')]
public int $filial;
#[Assert\NotBlank]
#[Assert\Type('integer')]
public int $timezone = 3;
#[Assert\NotBlank]
#[Assert\Type('integer')]
public int $schedident;
#[Assert\NotBlank]
#[Assert\Type('string')]
public string $rnum;
#[Assert\NotBlank]
#[Assert\Type('integer')]
public int $specialist;
public function resetveToArray(): array
{
$timeParts = explode('-', $this->time);
return [
'date' => $this->workDate,
'st' => $timeParts[0],
'en' => $timeParts[1],
'services' => $this->services,
'filial' => $this->filial,
'timezone' => $this->timezone,
'schedident' => $this->schedident,
'rnum' => $this->rnum,
'dcode' => $this->specialist,
];
}
public function toJson(): string
{
return json_encode($this->resetveToArray(), JSON_UNESCAPED_SLASHES);
}
public function toArray(): array
{
return [
'accept' => $this->accept,
'fio' => $this->fio,
'captcha' => $this->captcha,
'email' => $this->email,
'phone' => $this->phone,
'reserve' => $this->toJson()
];
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
namespace App\Dto;
use Symfony\Bundle\MakerBundle\Str;
// use Symfony\Component\Validator\Constraints as Assert;
class CalltouchCreateRequestDto
{
public int $regionId;
public string $requestNumber;
public string $subject;
public string $requestUrl;
public string $requestDate;
public ?string $sessionId;
public ?string $phoneNumber;
public ?string $email;
public ?string $fio;
public array $addTags;
public array $customSources;
public array $customFields;
public function toArray(): array
{
return [
'requestNumber' => $this->requestNumber,
'subject' => $this->subject,
'requestUrl' => $this->requestUrl,
'requestDate' => $this->requestDate,
'sessionId' => $this->sessionId,
'phoneNumber' => $this->phoneNumber,
'email' => $this->email,
'fio' => $this->fio,
'addTags' => $this->addTags,
'customSources' => $this->customSources,
'customFields' => $this->customFields
];
}
}
+81
View File
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace App\Dto\Content;
use Symfony\Component\HttpFoundation\Request;
final readonly class ContentFilterDto
{
public function __construct(
public ?int $regionId = null,
public ?bool $active = null,
public ?string $alias = null,
public ?string $search = null,
) {
}
/**
* @param ?bool $defaultActive если задан (например, true), подставляется,
* когда query-параметр `active` отсутствует или пустой.
* Легаси: в старых list-эндпоинтах News/Promo/MedicalCenter/SiteService
* при отсутствии `active` подразумевалось active = true.
*/
public static function fromRequest(Request $request, ?bool $defaultActive = null): self
{
$active = self::nullableBool($request->query->get('active'));
return new self(
regionId: self::positiveInt($request->query->get('regionId', $request->query->get('region_id'))),
active: $active ?? $defaultActive,
alias: self::nonEmptyString($request->query->get('alias')),
search: self::nonEmptyString($request->query->get('search', $request->query->get('q'))),
);
}
/**
* Symfony QueryBag может отдать массив при ?regionId[]=… — не передаём его в is_numeric (TypeError в PHP 8).
*/
private static function positiveInt(mixed $value): ?int
{
if ($value === null || $value === '' || !is_scalar($value) || !is_numeric($value)) {
return null;
}
$value = (int) $value;
return $value > 0 ? $value : null;
}
/**
* При ?active[]=… query->get вернёт массив — отбрасываем без вызова filter_var по нему.
*/
private static function nullableBool(mixed $value): ?bool
{
if ($value === null || $value === '') {
return null;
}
if (!is_scalar($value)) {
return null;
}
if (is_bool($value)) {
return $value;
}
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
private static function nonEmptyString(mixed $value): ?string
{
if (!is_string($value)) {
return null;
}
$value = trim($value);
return $value !== '' ? $value : null;
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Dto;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
class FileUploadDto
{
#[Assert\NotBlank(message: 'Файл не должен быть пустым')]
#[Assert\File(
maxSize: '5M',
mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
mimeTypesMessage: 'Пожалуйста, загрузите файл в формате JPG, PNG или webp'
)]
public ?UploadedFile $file = null;
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class RegionDto
{
#[Assert\NotNull(message: "regionId не может быть пустым")]
#[Assert\Type(type: 'integer', message: "regionId должен быть целым числом")]
#[Assert\Positive(message: "regionId должен быть положительным числом")]
public ?int $regionId;
}
+42
View File
@@ -0,0 +1,42 @@
<?php
namespace App\Dto;
// use Symfony\Component\Validator\Constraints as Assert;
class RegistrationDto
{
public bool $accept = false;
public string $firstName;
public string $lastName;
public string $middleName;
public bool $refuseCall = true;
public bool $refuseSms = true;
public string $birthDate;
public string $email;
public string $phone;
public bool $confirmed;
public int $gender = 1; // male 1 | female 0
public string $checkData;
public string $captcha;
public string $snils;
public function toArray(): array
{
return [
'accept' => $this->accept,
'firstName' => $this->firstName,
'lastName' => $this->lastName,
'middleName' => $this->middleName,
'refuseCall' => $this->refuseCall,
'refuseSms' => $this->refuseSms,
'email' => $this->email,
'phone' => $this->phone,
'confirmed' => $this->confirmed,
'gender' => $this->gender,
'checkData' => $this->checkData,
'captcha' => $this->captcha,
'snils' => $this->snils
];
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
final class ReviewInputDto
{
#[Assert\NotNull(message: 'Укажите идентификатор специалиста')]
#[Assert\Type(type: 'numeric', message: 'specialistId должен быть числом')]
public mixed $specialistId = null;
#[Assert\NotNull(groups: ['review_update'], message: 'Укажите active')]
public ?bool $active = null;
#[Assert\NotBlank(message: 'Укажите текст отзыва')]
public ?string $message = null;
#[Assert\NotBlank(message: 'Укажите автора')]
#[Assert\Length(max: 255)]
public ?string $author = null;
#[Assert\NotNull(message: 'Укажите оценку')]
#[Assert\Range(min: 1, max: 5, notInRangeMessage: 'Оценка должна быть от {{ min }} до {{ max }}')]
public ?float $rating = null;
#[Assert\Length(max: 255)]
public ?string $source = null;
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Dto;
class ScheduleDayDto
{
public function __construct(
public string $schedident,
public string $rnum,
public string $dcode,
public int $filial,
public array $intervals,
public string $depnum,
public bool $isFree
) {}
}
class ScheduleIntervalDto
{
public function __construct(
public string $time,
public bool $isFree
) {}
}
class ScheduleResponseDto
{
public function __construct(
public array $schedule = [],
public array $nearestDate = []
) {}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class ScheduleDto
{
#[Assert\NotNull(message: "st не может быть пустым")]
#[Assert\Type(type: 'integer', message: "st должен быть целым числом")]
#[Assert\Positive(message: "st должен быть положительным числом")]
public ?int $st;
#[Assert\NotNull(message: "en не может быть пустым")]
#[Assert\Type(type: 'integer', message: "en должен быть целым числом")]
#[Assert\Positive(message: "en должен быть положительным числом")]
public ?int $en;
#[Assert\NotNull(message: "dcode не может быть пустым")]
#[Assert\Type(type: 'integer', message: "dcode должен быть целым числом")]
#[Assert\Positive(message: "dcode должен быть положительным числом")]
public ?int $dcode;
#[Assert\NotNull(message: "filial не может быть пустым")]
#[Assert\Type(type: 'integer', message: "filial должен быть целым числом")]
#[Assert\Positive(message: "filial должен быть положительным числом")]
public ?int $filial;
#[Assert\NotNull(message: "onlineMode не может быть пустым")]
#[Assert\Type(type: 'boolean', message: "onlineMode должен быть булевом")]
public ?bool $onlineMode;
public function toQueryString(): string
{
return http_build_query([
'st' => $this->st,
'en' => $this->en,
'dcode' => $this->dcode,
'onlineMode' => $this->onlineMode,
'filialId' => $this->filial,
]);
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Dto;
use Symfony\Component\HttpFoundation\Request;
class SpecialistFilterDto
{
public ?bool $active = null;
public ?array $sType = null;
public ?int $regionId = null;
public ?array $dcode = null;
public ?string $department = null;
public ?string $alias = null;
public ?array $tags = null;
public ?bool $onlineMode = null;
public ?string $dateFrom = null;
public ?string $dateTo = null;
public ?array $kodoper = null;
public ?string $search = null;
public ?string $category = null;
public ?string $patientAge = null;
public ?bool $displaySchedule = null;
public ?string $orderBy = null;
public ?int $filial = null;
public ?array $sFilial = null;
public ?bool $kiosk = null;
public static function fromRequest(Request $request): self
{
$dto = new self();
$dto->active = $request->query->has('active')
? filter_var($request->query->get('active'), FILTER_VALIDATE_BOOLEAN)
: null;
$dto->sType = $request->query->all('sType');
$dto->regionId = $request->query->getInt('regionId', 0) ?: null;
$dto->dcode = $request->query->all('dcode');
$dto->department = $request->query->get('department');
$dto->alias = $request->query->get('alias');
$dto->tags = $request->query->all('tags');
$dto->onlineMode = $request->query->has('onlineMode')
? filter_var($request->query->get('onlineMode'), FILTER_VALIDATE_BOOLEAN)
: null;
$dto->dateFrom = $request->query->get('dateFrom');
$dto->dateTo = $request->query->get('dateTo');
$dto->kodoper = $request->query->all('kodoper');
$dto->search = $request->query->get('search');
$dto->category = $request->query->get('category');
$dto->patientAge = $request->query->get('patientAge');
$dto->displaySchedule = $request->query->has('displaySchedule')
? filter_var($request->query->get('displaySchedule'), FILTER_VALIDATE_BOOLEAN)
: null;
$dto->orderBy = $request->query->get('orderBy');
$dto->filial = $request->query->getInt('filial', 0) ?: null;
$dto->sFilial = $request->query->all('sFilial');
$dto->kiosk = $request->query->has('isKiosk')
? filter_var($request->query->get('isKiosk'), FILTER_VALIDATE_BOOLEAN)
: null;
return $dto;
}
public function toArray(): array
{
return [
'active' => $this->active,
'sType' => $this->sType,
'regionId' => $this->regionId,
'dcode' => $this->dcode,
'department' => $this->department,
'alias' => $this->alias,
'tags' => $this->tags,
'onlineMode' => $this->onlineMode,
'dateFrom' => $this->dateFrom,
'dateTo' => $this->dateTo,
'kodoper' => $this->kodoper,
'search' => $this->search,
'category' => $this->category,
'patientAge' => $this->patientAge,
'displaySchedule' => $this->displaySchedule,
'orderBy' => $this->orderBy,
'filial' => $this->filial,
'sFilial' => $this->sFilial,
'kiosk' => $this->kiosk
];
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class UserAuthDto
{
#[Assert\Email]
#[Assert\NotBlank(message: "Полное email не может быть пустым")]
public ?string $email;
#[Assert\NotNull(message: "password не может быть пустым")]
#[Assert\Length(
min: 8,
max: 100,
minMessage: 'Пароль должен содержать минимум 8 символов.',
maxMessage: 'Пароль не должен превышать 100 символов.'
)]
public ?string $password;
#[Assert\NotNull(message: "regionId не может быть пустым")]
#[Assert\Type(type: 'integer', message: "regionId должен быть целым числом")]
#[Assert\Positive(message: "regionId должен быть положительным числом")]
public ?int $regionId;
#[Assert\NotNull(message: "UID не может быть пустым")]
#[Assert\Type(type: 'integer', message: "UID должен быть целым числом")]
#[Assert\Positive(message: "UID должен быть положительным числом")]
public ?int $uid;
#[Assert\NotBlank(message: "Дата рождения не может быть пустой")]
#[Assert\LessThan(
value: "now",
message: "Дата рождения не может быть в будущем"
)]
public ?string $birthDate;
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class UserLoginDto
{
#[Assert\Email]
#[Assert\NotBlank(message: "Полное email не может быть пустым")]
public ?string $email;
#[Assert\NotNull(message: "password не может быть пустым")]
#[Assert\Length(
min: 8,
max: 100,
minMessage: 'Пароль должен содержать минимум 8 символов.',
maxMessage: 'Пароль не должен превышать 100 символов.'
)]
public ?string $password;
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
class UserUidAuthDto
{
#[Assert\NotNull(message: "UID не может быть пустым")]
#[Assert\Type(type: 'integer', message: "UID должен быть целым числом")]
#[Assert\Positive(message: "UID должен быть положительным числом")]
public ?int $uid;
#[Assert\NotBlank(message: "Дата рождения не может быть пустой")]
#[Assert\LessThan(
value: "now",
message: "Дата рождения не может быть в будущем"
)]
public ?string $birthDate;
}
View File
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Entity;
use App\Repository\AlertSmsRepository;
use Doctrine\ORM\Mapping as ORM;
use DateTimeInterface;
#[ORM\Entity(repositoryClass: AlertSmsRepository::class)]
class AlertSms
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\OneToOne(targetEntity: Record::class, inversedBy: 'alertSms', cascade: ['persist', 'remove'])]
private ?Record $record = null;
#[ORM\Column(type: 'datetime')]
private ?DateTimeInterface $dateCreate = null;
#[ORM\Column(type: 'text')]
private ?string $response = null;
public function getId(): ?int
{
return $this->id;
}
public function getRecord(): ?Record
{
return $this->record;
}
public function setRecord(?Record $record): self
{
$this->record = $record;
return $this;
}
public function getDateCreate(): ?DateTimeInterface
{
return $this->dateCreate;
}
public function setDateCreate(DateTimeInterface $dateCreate): self
{
$this->dateCreate = $dateCreate;
return $this;
}
public function getResponse(): ?string
{
return $this->response;
}
public function setResponse(string $response): self
{
$this->response = $response;
return $this;
}
}
+191
View File
@@ -0,0 +1,191 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\ArticleRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
#[ORM\Table(name: 'article')]
#[ORM\Index(name: 'idx_article_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_article_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Article
{
use UpdateTimestampTrait;
#[Groups(['article:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
private ?int $id = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $name = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(name: 'preview_picture', type: Types::TEXT, nullable: true)]
private ?string $previewPicture = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
private ?bool $active = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $doctors = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $services = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
private ?int $regionId = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $alias = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $anons = null;
#[Groups(['article:read', 'article:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $content = null;
#[Groups(['article:read'])]
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeInterface $updateAt = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): static
{
$this->name = $name;
return $this;
}
public function getPreviewPicture(): ?string
{
return $this->previewPicture;
}
public function setPreviewPicture(?string $previewPicture): static
{
$this->previewPicture = $previewPicture;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): static
{
$this->active = $active;
return $this;
}
public function getDoctors(): ?array
{
return $this->doctors;
}
public function setDoctors(?array $doctors): static
{
$this->doctors = $doctors;
return $this;
}
public function getServices(): ?array
{
return $this->services;
}
public function setServices(?array $services): static
{
$this->services = $services;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): static
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): static
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): static
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): static
{
$this->content = $content;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): static
{
$this->updateAt = $updateAt;
return $this;
}
}
+93
View File
@@ -0,0 +1,93 @@
<?php
namespace App\Entity;
use App\Repository\BannerRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=BannerRepository::class)
*/
class Banner
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $href;
/**
* @ORM\Column(type="string", length=255)
*/
private $src;
/**
* @ORM\Column(type="boolean")
*/
private $active;
/**
* @ORM\OneToOne(targetEntity=City::class, inversedBy="banner")
* @ORM\JoinColumn(nullable=false)
*/
private $city;
public function getId(): ?int
{
return $this->id;
}
public function getHref(): ?string
{
return $this->href;
}
public function setHref(string $href): self
{
$this->href = $href;
return $this;
}
public function getSrc(): ?string
{
return $this->src;
}
public function setSrc(string $src): self
{
$this->src = $src;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getCity(): ?City
{
return $this->city;
}
public function setCity(City $city): self
{
$this->city = $city;
return $this;
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\Entity\Behavior;
use Doctrine\ORM\Mapping as ORM;
/**
* Требует у класса-сущности свойство `$updateAt` (mapped column).
*
* @property \DateTimeInterface|null $updateAt
*/
trait UpdateTimestampTrait
{
#[ORM\PrePersist]
public function setInitialUpdateAt(): void
{
if ($this->updateAt === null) {
$this->updateAt = new \DateTimeImmutable();
}
}
#[ORM\PreUpdate]
public function refreshUpdateAt(): void
{
$this->updateAt = new \DateTimeImmutable();
}
}
+129
View File
@@ -0,0 +1,129 @@
<?php
namespace App\Entity;
use App\Repository\DepartmentRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: DepartmentRepository::class)]
#[ORM\Table(name: 'departments', uniqueConstraints: [
new ORM\UniqueConstraint(name: 'uniq_department_did', columns: ['did'])
])]
class Department
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(type: 'bigint')]
private ?string $did = null;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(type: 'string', length: 255)]
private ?string $name = null;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(type: 'boolean')]
private ?bool $onlineMode = null;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(type: 'string', length: 255)]
private ?string $alias = null;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(type: 'boolean', options: ['default' => true])]
private bool $active = true;
#[Groups(['department:read', 'department:write'])]
#[ORM\Column(name: 'group_name', type: 'string', length: 255, nullable: true)]
private ?string $groupName = null;
public function getId(): ?int
{
return $this->id;
}
public function getDid(): ?string
{
return $this->did;
}
public function setDid(?string $did): self
{
$this->did = $did;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getOnlineMode(): ?bool
{
return $this->onlineMode;
}
public function setOnlineMode(?bool $onlineMode): self
{
$this->onlineMode = $onlineMode;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getGroupName(): ?string
{
return $this->groupName;
}
public function setGroupName(?string $groupName): self
{
$this->groupName = $groupName;
return $this;
}
public function toArray(): array
{
return [
'did' => $this->getDid(),
'name' => $this->getName(),
'onlineMode' => $this->getOnlineMode(),
'alias' => $this->getAlias(),
'active' => $this->getActive(),
'groupName' => $this->getGroupName(),
];
}
}
+405
View File
@@ -0,0 +1,405 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\DiseaseRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: DiseaseRepository::class)]
#[ORM\Table(name: 'disease')]
#[ORM\Index(name: 'idx_disease_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_disease_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Disease
{
use UpdateTimestampTrait;
#[Groups(['disease:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
private ?int $id = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $name = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'preview_picture', type: Types::TEXT, nullable: true)]
private ?string $previewPicture = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
private ?bool $active = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
private ?int $regionId = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $alias = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $anons = null;
#[Groups(['disease:read'])]
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeInterface $updateAt = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'hide_picture', type: Types::BOOLEAN, nullable: true)]
private ?bool $hidePicture = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'read_time', type: Types::TEXT, nullable: true)]
private ?string $readTime = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'diseases_name', type: Types::TEXT, nullable: true)]
private ?string $diseasesName = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'tags_important', type: 'jsonb', nullable: true)]
private ?array $tagsImportant = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $tags = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'diseases_other_name', type: Types::TEXT, nullable: true)]
private ?string $diseasesOtherName = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $symptom = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $staff = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'link_services', type: 'jsonb', nullable: true)]
private ?array $linkServices = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'staff_list', type: 'jsonb', nullable: true)]
private ?array $staffList = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'staff_post', type: 'jsonb', nullable: true)]
private ?array $staffPost = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'staff_post_exclude', type: 'jsonb', nullable: true)]
private ?array $staffPostExclude = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'link_faq', type: 'jsonb', nullable: true)]
private ?array $linkFaq = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $bibliography = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(name: 'staff_check', type: 'jsonb', nullable: true)]
private ?array $staffCheck = null;
#[Groups(['disease:read', 'disease:write'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $content = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): static
{
$this->id = $id;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): static
{
$this->name = $name;
return $this;
}
public function getPreviewPicture(): ?string
{
return $this->previewPicture;
}
public function setPreviewPicture(?string $previewPicture): static
{
$this->previewPicture = $previewPicture;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): static
{
$this->active = $active;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): static
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): static
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): static
{
$this->anons = $anons;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): static
{
$this->updateAt = $updateAt;
return $this;
}
public function getHidePicture(): ?bool
{
return $this->hidePicture;
}
public function setHidePicture(?bool $hidePicture): static
{
$this->hidePicture = $hidePicture;
return $this;
}
public function getReadTime(): ?string
{
return $this->readTime;
}
public function setReadTime(?string $readTime): static
{
$this->readTime = $readTime;
return $this;
}
public function getDiseasesName(): ?string
{
return $this->diseasesName;
}
public function setDiseasesName(?string $diseasesName): static
{
$this->diseasesName = $diseasesName;
return $this;
}
public function getTagsImportant(): ?array
{
return $this->tagsImportant;
}
public function setTagsImportant(?array $tagsImportant): static
{
$this->tagsImportant = $tagsImportant;
return $this;
}
public function getTags(): ?array
{
return $this->tags;
}
public function setTags(?array $tags): static
{
$this->tags = $tags;
return $this;
}
public function getDiseasesOtherName(): ?string
{
return $this->diseasesOtherName;
}
public function setDiseasesOtherName(?string $diseasesOtherName): static
{
$this->diseasesOtherName = $diseasesOtherName;
return $this;
}
public function getSymptom(): ?string
{
return $this->symptom;
}
public function setSymptom(?string $symptom): static
{
$this->symptom = $symptom;
return $this;
}
public function getStaff(): ?string
{
return $this->staff;
}
public function setStaff(?string $staff): static
{
$this->staff = $staff;
return $this;
}
public function getLinkServices(): ?array
{
return $this->linkServices;
}
public function setLinkServices(?array $linkServices): static
{
$this->linkServices = $linkServices;
return $this;
}
public function getStaffList(): ?array
{
return $this->staffList;
}
public function setStaffList(?array $staffList): static
{
$this->staffList = $staffList;
return $this;
}
public function getStaffPost(): ?array
{
return $this->staffPost;
}
public function setStaffPost(?array $staffPost): static
{
$this->staffPost = $staffPost;
return $this;
}
public function getStaffPostExclude(): ?array
{
return $this->staffPostExclude;
}
public function setStaffPostExclude(?array $staffPostExclude): static
{
$this->staffPostExclude = $staffPostExclude;
return $this;
}
public function getLinkFaq(): ?array
{
return $this->linkFaq;
}
public function setLinkFaq(?array $linkFaq): static
{
$this->linkFaq = $linkFaq;
return $this;
}
public function getBibliography(): ?string
{
return $this->bibliography;
}
public function setBibliography(?string $bibliography): static
{
$this->bibliography = $bibliography;
return $this;
}
public function getStaffCheck(): ?array
{
return $this->staffCheck;
}
public function setStaffCheck(?array $staffCheck): static
{
$this->staffCheck = $staffCheck;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): static
{
$this->content = $content;
return $this;
}
}
+231
View File
@@ -0,0 +1,231 @@
<?php
namespace App\Entity;
use App\Repository\FilialRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: FilialRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Filial
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups(['filial:read'])]
private int $id;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'integer')]
private int $fid;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'string', length: 255)]
private string $address;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $regionId = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $siteId = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'boolean', options: ['default' => true])]
private bool $active = true;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $company = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $shortName = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $phone = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $policy = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $picture = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $email = null;
#[Groups(['filial:read', 'filial:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $origin = null;
public function getId(): ?int
{
return $this->id;
}
public function getFid(): ?int
{
return $this->fid;
}
public function setFid(int $fid): self
{
$this->fid = $fid;
return $this;
}
public function getSiteId(): ?int
{
return $this->siteId;
}
public function setSiteId(?int $siteId): self
{
$this->siteId = $siteId;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getAddress(): ?string
{
return $this->address;
}
public function setAddress(?string $address): self
{
$this->address = $address;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getCompany(): ?string
{
return $this->company;
}
public function setCompany(?string $company): self
{
$this->company = $company;
return $this;
}
public function getShortName(): ?string
{
return $this->shortName;
}
public function setShortName(?string $shortName): static
{
$this->shortName = $shortName;
return $this;
}
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(?string $phone): static
{
$this->phone = $phone;
return $this;
}
public function getPolicy(): ?string
{
return $this->policy;
}
public function setPolicy(?string $policy): static
{
$this->policy = $policy;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): static
{
$this->picture = $picture;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): static
{
$this->email = $email;
return $this;
}
public function getOrigin(): ?string
{
return $this->origin;
}
public function setOrigin(?string $origin): static
{
$this->origin = $origin;
return $this;
}
}
+128
View File
@@ -0,0 +1,128 @@
<?php
namespace App\Entity;
use App\Repository\IdoctorRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: IdoctorRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Idoctor
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'bigint')]
private int $id;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'bigint')]
private int $dcode;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'bigint')]
private int $department;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'integer')]
private int $filial;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'string', length: 255)]
private string $nearestDate;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'boolean')]
private bool $onlineMode;
#[Groups(['idoctor:read'])]
#[ORM\Column(type: 'datetime', nullable: true)]
private ?\DateTimeInterface $updated = null;
#[ORM\PreFlush]
public function onPreFlush()
{
$this->updated = new \DateTime("now");
}
public function getId(): ?string
{
return $this->id;
}
public function getDcode(): ?string
{
return $this->dcode;
}
public function setDcode(string $dcode): self
{
$this->dcode = $dcode;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDepartment(): ?string
{
return $this->department;
}
public function setDepartment(string $department): self
{
$this->department = $department;
return $this;
}
public function getFilial(): ?int
{
return $this->filial;
}
public function setFilial(int $filial): self
{
$this->filial = $filial;
return $this;
}
public function getNearestDate(): ?string
{
return $this->nearestDate;
}
public function setNearestDate(string $nearestDate): self
{
$this->nearestDate = $nearestDate;
return $this;
}
public function getOnlineMode(): bool
{
return $this->onlineMode;
}
public function setOnlineMode(bool $onlineMode): self
{
$this->onlineMode = $onlineMode;
return $this;
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
namespace App\Entity;
use App\Repository\LocationRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: LocationRepository::class)]
#[ORM\Index(columns: ['specialist_id'], name: 'idx_location_specialist_id')]
#[ORM\HasLifecycleCallbacks]
class Location
{
#[Groups(['from.specialist:read', 'location:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'bigint')]
private int $id;
#[Groups(['from.specialist:read', 'location:read', 'location:write'])]
#[ORM\Column(type: 'bigint', nullable: true)]
private ?int $dcode;
#[Groups(['from.specialist:read','location:read', 'location:write'])]
#[ORM\Column(type: 'bigint')]
private int $department;
#[Groups(['from.specialist:read', 'location:read', 'location:write'])]
#[ORM\Column(type: 'integer')]
private int $filial;
#[Groups(['from.specialist:read', 'location:read', 'location:write'])]
#[ORM\Column(type: 'boolean')]
private bool $onlineMode;
#[Groups(['from.specialist:read', 'location:read', 'location:write'])]
#[ORM\Column(type: 'boolean')]
private bool $active;
#[Groups(['from.specialist:read', 'location:read', 'location:write'])]
#[ORM\Column(type: 'date', nullable: true)]
private ?\DateTimeInterface $nearestDate = null;
#[Groups(['location:read'])]
#[ORM\ManyToOne(
targetEntity: Specialist::class,
inversedBy: 'locations',
cascade: ['persist']
)]
#[ORM\JoinColumn(name: "specialist_id", referencedColumnName: "id")]
private ?Specialist $specialist;
public function getId(): int
{
return $this->id;
}
public function getDcode(): ?int
{
return $this->dcode;
}
public function setDcode(?int $dcode): self
{
$this->dcode = (int) $dcode;
return $this;
}
public function getDepartment(): int
{
return $this->department;
}
public function setDepartment(int $department): self
{
$this->department = $department;
return $this;
}
public function getFilial(): int
{
return $this->filial;
}
public function setFilial(int $filial): self
{
$this->filial = $filial;
return $this;
}
public function getOnlineMode(): bool
{
return $this->onlineMode;
}
public function setOnlineMode(bool $onlineMode): self
{
$this->onlineMode = $onlineMode;
return $this;
}
public function getActive(): bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getNearestDate(): ?\DateTimeInterface
{
return $this->nearestDate;
}
public function setNearestDate(?\DateTimeInterface $nearestDate): self
{
$this->nearestDate = $nearestDate;
return $this;
}
public function getSpecialist(): ?Specialist
{
return $this->specialist;
}
public function setSpecialist(?Specialist $specialist): self
{
$this->specialist = $specialist;
return $this;
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Entity;
use App\Repository\MarkKioskRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: MarkKioskRepository::class)]
class MarkKiosk
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $pcode = null;
#[ORM\Column]
private ?int $filial = null;
#[ORM\Column]
private ?\DateTimeImmutable $created_at = null;
#[ORM\Column]
private ?\DateTimeImmutable $modify_at = null;
#[ORM\Column(nullable: true)]
private ?bool $result = null;
public function getId(): ?int
{
return $this->id;
}
public function getPcode(): ?string
{
return $this->pcode;
}
public function setPcode(string $pcode): static
{
$this->pcode = $pcode;
return $this;
}
public function getFilial(): ?int
{
return $this->filial;
}
public function setFilial(int $filial): static
{
$this->filial = $filial;
return $this;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeImmutable $created_at): static
{
$this->created_at = $created_at;
return $this;
}
public function getModifyAt(): ?\DateTimeImmutable
{
return $this->modify_at;
}
public function setModifyAt(\DateTimeImmutable $modify_at): static
{
$this->modify_at = $modify_at;
return $this;
}
public function isResult(): ?bool
{
return $this->result;
}
public function setResult(?bool $result): static
{
$this->result = $result;
return $this;
}
}
+515
View File
@@ -0,0 +1,515 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\MedicalCenterRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: MedicalCenterRepository::class)]
#[ORM\Table(name: 'medical_center')]
#[ORM\HasLifecycleCallbacks]
class MedicalCenter
{
use UpdateTimestampTrait;
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
#[Groups(['medical_center:read'])]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $name = null;
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?bool $active = null;
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?int $regionId = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $alias = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $anons = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
#[Groups(['medical_center:read'])]
private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'kod_uslug', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $kodUslug = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $doctors = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $services = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $articles = null;
#[ORM\Column(name: 'txt_up', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $txtUp = null;
#[ORM\Column(name: 'main_link_staff', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $mainLinkStaff = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $contraindications = null;
#[ORM\Column(name: 'hide_picture', type: Types::INTEGER, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?int $hidePicture = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $indications = null;
#[ORM\Column(name: 'link_sale', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $linkSale = null;
#[ORM\Column(name: 'plus_list', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $plusList = null;
#[ORM\Column(name: 'plus_text', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $plusText = null;
#[ORM\Column(name: 'plus_title', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $plusTitle = null;
#[ORM\Column(name: 'process_text', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $processText = null;
#[ORM\Column(name: 'process_title', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $processTitle = null;
#[ORM\Column(name: 'services_list', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $servicesList = null;
#[ORM\Column(name: 'services_photos', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $servicesPhotos = null;
#[ORM\Column(name: 'services_title', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $servicesTitle = null;
#[ORM\Column(name: 'sort_staff', type: 'jsonb', nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?array $sortStaff = null;
#[ORM\Column(name: 'training_text', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $trainingText = null;
#[ORM\Column(name: 'training_text_title', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $trainingTextTitle = null;
#[ORM\Column(name: 'why_text', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $whyText = null;
#[ORM\Column(name: 'why_title', type: Types::TEXT, nullable: true)]
#[Groups(['medical_center:read', 'medical_center:write'])]
private ?string $whyTitle = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): self
{
$this->id = $id;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): self
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): self
{
$this->updateAt = $updateAt;
return $this;
}
public function getKodUslug(): ?array
{
return $this->kodUslug;
}
public function setKodUslug(?array $kodUslug): self
{
$this->kodUslug = $kodUslug;
return $this;
}
public function getDoctors(): ?array
{
return $this->doctors;
}
public function setDoctors(?array $doctors): self
{
$this->doctors = $doctors;
return $this;
}
public function getServices(): ?array
{
return $this->services;
}
public function setServices(?array $services): self
{
$this->services = $services;
return $this;
}
public function getArticles(): ?array
{
return $this->articles;
}
public function setArticles(?array $articles): self
{
$this->articles = $articles;
return $this;
}
public function getTxtUp(): ?array
{
return $this->txtUp;
}
public function setTxtUp(?array $txtUp): self
{
$this->txtUp = $txtUp;
return $this;
}
public function getMainLinkStaff(): ?string
{
return $this->mainLinkStaff;
}
public function setMainLinkStaff(?string $mainLinkStaff): self
{
$this->mainLinkStaff = $mainLinkStaff;
return $this;
}
public function getContraindications(): ?array
{
return $this->contraindications;
}
public function setContraindications(?array $contraindications): self
{
$this->contraindications = $contraindications;
return $this;
}
public function getHidePicture(): ?int
{
return $this->hidePicture;
}
public function setHidePicture(?int $hidePicture): self
{
$this->hidePicture = $hidePicture;
return $this;
}
public function getIndications(): ?array
{
return $this->indications;
}
public function setIndications(?array $indications): self
{
$this->indications = $indications;
return $this;
}
public function getLinkSale(): ?array
{
return $this->linkSale;
}
public function setLinkSale(?array $linkSale): self
{
$this->linkSale = $linkSale;
return $this;
}
public function getPlusList(): ?array
{
return $this->plusList;
}
public function setPlusList(?array $plusList): self
{
$this->plusList = $plusList;
return $this;
}
public function getPlusText(): ?string
{
return $this->plusText;
}
public function setPlusText(?string $plusText): self
{
$this->plusText = $plusText;
return $this;
}
public function getPlusTitle(): ?string
{
return $this->plusTitle;
}
public function setPlusTitle(?string $plusTitle): self
{
$this->plusTitle = $plusTitle;
return $this;
}
public function getProcessText(): ?string
{
return $this->processText;
}
public function setProcessText(?string $processText): self
{
$this->processText = $processText;
return $this;
}
public function getProcessTitle(): ?string
{
return $this->processTitle;
}
public function setProcessTitle(?string $processTitle): self
{
$this->processTitle = $processTitle;
return $this;
}
public function getServicesList(): ?array
{
return $this->servicesList;
}
public function setServicesList(?array $servicesList): self
{
$this->servicesList = $servicesList;
return $this;
}
public function getServicesPhotos(): ?array
{
return $this->servicesPhotos;
}
public function setServicesPhotos(?array $servicesPhotos): self
{
$this->servicesPhotos = $servicesPhotos;
return $this;
}
public function getServicesTitle(): ?string
{
return $this->servicesTitle;
}
public function setServicesTitle(?string $servicesTitle): self
{
$this->servicesTitle = $servicesTitle;
return $this;
}
public function getSortStaff(): ?array
{
return $this->sortStaff;
}
public function setSortStaff(?array $sortStaff): self
{
$this->sortStaff = $sortStaff;
return $this;
}
public function getTrainingText(): ?string
{
return $this->trainingText;
}
public function setTrainingText(?string $trainingText): self
{
$this->trainingText = $trainingText;
return $this;
}
public function getTrainingTextTitle(): ?string
{
return $this->trainingTextTitle;
}
public function setTrainingTextTitle(?string $trainingTextTitle): self
{
$this->trainingTextTitle = $trainingTextTitle;
return $this;
}
public function getWhyText(): ?string
{
return $this->whyText;
}
public function setWhyText(?string $whyText): self
{
$this->whyText = $whyText;
return $this;
}
public function getWhyTitle(): ?string
{
return $this->whyTitle;
}
public function setWhyTitle(?string $whyTitle): self
{
$this->whyTitle = $whyTitle;
return $this;
}
}
+277
View File
@@ -0,0 +1,277 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\NewsRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: NewsRepository::class)]
#[ORM\Table(name: 'news')]
#[ORM\Index(name: 'idx_news_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_news_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class News
{
use UpdateTimestampTrait;
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
#[Groups(['news:read'])]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $name = null;
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?bool $active = null;
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?int $regionId = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $alias = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $anons = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
#[Groups(['news:read'])]
private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'link_el_price', type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $linkElPrice = null;
#[ORM\Column(name: 'short_name', type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $shortName = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $timer = null;
#[ORM\Column(name: 'timer_bg', type: Types::TEXT, nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?string $timerBg = null;
#[ORM\Column(name: 'form_order', type: 'jsonb', nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?array $formOrder = null;
#[ORM\Column(name: 'link_services', type: 'jsonb', nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?array $linkServices = null;
#[ORM\Column(name: 'link_staff', type: 'jsonb', nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?array $linkStaff = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['news:read', 'news:write'])]
private ?array $photos = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): self
{
$this->id = $id;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): self
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): self
{
$this->updateAt = $updateAt;
return $this;
}
public function getLinkElPrice(): ?string
{
return $this->linkElPrice;
}
public function setLinkElPrice(?string $linkElPrice): self
{
$this->linkElPrice = $linkElPrice;
return $this;
}
public function getShortName(): ?string
{
return $this->shortName;
}
public function setShortName(?string $shortName): self
{
$this->shortName = $shortName;
return $this;
}
public function getTimer(): ?string
{
return $this->timer;
}
public function setTimer(?string $timer): self
{
$this->timer = $timer;
return $this;
}
public function getTimerBg(): ?string
{
return $this->timerBg;
}
public function setTimerBg(?string $timerBg): self
{
$this->timerBg = $timerBg;
return $this;
}
public function getFormOrder(): ?array
{
return $this->formOrder;
}
public function setFormOrder(?array $formOrder): self
{
$this->formOrder = $formOrder;
return $this;
}
public function getLinkServices(): ?array
{
return $this->linkServices;
}
public function setLinkServices(?array $linkServices): self
{
$this->linkServices = $linkServices;
return $this;
}
public function getLinkStaff(): ?array
{
return $this->linkStaff;
}
public function setLinkStaff(?array $linkStaff): self
{
$this->linkStaff = $linkStaff;
return $this;
}
public function getPhotos(): ?array
{
return $this->photos;
}
public function setPhotos(?array $photos): self
{
$this->photos = $photos;
return $this;
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
namespace App\Entity;
use App\Repository\PriceDepartmentRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: PriceDepartmentRepository::class)]
class PriceDepartment
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column]
#[Groups(['departmentPrice:write'])]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Groups(['departmentPrice:read', 'departmentPrice:write'])]
private ?string $name = null;
#[ORM\Column(nullable: true)]
#[Groups(['departmentPrice:read', 'departmentPrice:write'])]
private ?int $groupId = null;
#[ORM\Column(nullable: true)]
#[Groups(['departmentPrice:read', 'departmentPrice:write'])]
private ?int $doctCount = null;
#[ORM\Column]
#[Groups(['departmentPrice:read', 'departmentPrice:write'])]
private ?bool $viewInWeb = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getGroupId(): ?int
{
return $this->groupId;
}
public function setGroupId(?int $groupId): static
{
$this->groupId = $groupId;
return $this;
}
public function getDoctCount(): ?int
{
return $this->doctCount;
}
public function setDoctCount(?int $doctCount): static
{
$this->doctCount = $doctCount;
return $this;
}
public function isViewInWeb(): ?bool
{
return $this->viewInWeb;
}
public function setViewInWeb(bool $viewInWeb): static
{
$this->viewInWeb = $viewInWeb;
return $this;
}
}
+246
View File
@@ -0,0 +1,246 @@
<?php
namespace App\Entity;
use App\Repository\PriceListRepository;
use Doctrine\DBAL\Types\Types;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: PriceListRepository::class)]
#[ORM\Index(name: 'idx_price_list_schname', columns: ['schname'])]
#[ORM\Index(name: 'idx_price_list_kodoper', columns: ['kodoper'])]
#[ORM\Index(name: 'idx_price_list_filial', columns: ['filial'])]
class PriceList
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column]
private ?int $id = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $kodoper = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $schname = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $specname = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(type: Types::BIGINT, nullable: true)]
private ?string $speccode = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(nullable: true)]
private ?array $priceInfo = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $discprice = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $structname = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $fname = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(nullable: true)]
private ?int $filial = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $comment = null;
#[ORM\Column(nullable: true)]
private ?int $mediaId = null;
#[Groups(['pricelist:read'])]
#[ORM\Column]
private ?\DateTime $dateUpdate = null;
#[Groups(['pricelist:read'])]
#[ORM\Column(nullable: true)]
private ?int $groupId = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $discpercent = null;
public function getId(): ?int
{
return $this->id;
}
public function getKodoper(): ?string
{
return $this->kodoper;
}
public function setKodoper(?string $kodoper): static
{
$this->kodoper = $kodoper;
return $this;
}
public function getSchname(): ?string
{
return $this->schname;
}
public function setSchname(?string $schname): static
{
$this->schname = $schname;
return $this;
}
public function getSpecname(): ?string
{
return $this->specname;
}
public function setSpecname(?string $specname): static
{
$this->specname = $specname;
return $this;
}
public function getSpeccode(): ?string
{
return $this->speccode;
}
public function setSpeccode(?string $speccode): static
{
$this->speccode = $speccode;
return $this;
}
public function getPriceInfo(): ?array
{
return $this->priceInfo;
}
public function setPriceInfo(?array $priceInfo): static
{
$this->priceInfo = $priceInfo;
return $this;
}
public function getDiscprice(): ?string
{
return $this->discprice;
}
public function setDiscprice(?string $discprice): static
{
$this->discprice = $discprice;
return $this;
}
public function getStructname(): ?string
{
return $this->structname;
}
public function setStructname(?string $structname): static
{
$this->structname = $structname;
return $this;
}
public function getFname(): ?string
{
return $this->fname;
}
public function setFname(?string $fname): static
{
$this->fname = $fname;
return $this;
}
public function getFilial(): ?int
{
return $this->filial;
}
public function setFilial(?int $filial): static
{
$this->filial = $filial;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): static
{
$this->comment = $comment;
return $this;
}
public function getMediaId(): ?int
{
return $this->mediaId;
}
public function setMediaId(?int $mediaId): static
{
$this->mediaId = $mediaId;
return $this;
}
public function getDateUpdate(): ?\DateTime
{
return $this->dateUpdate;
}
public function setDateUpdate(\DateTime $dateUpdate): static
{
$this->dateUpdate = $dateUpdate;
return $this;
}
public function getGroupId(): ?int
{
return $this->groupId;
}
public function setGroupId(?int $groupId): static
{
$this->groupId = $groupId;
return $this;
}
public function getDiscpercent(): ?string
{
return $this->discpercent;
}
public function setDiscpercent(?string $discpercent): static
{
$this->discpercent = $discpercent;
return $this;
}
}
+277
View File
@@ -0,0 +1,277 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\PromoRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: PromoRepository::class)]
#[ORM\Table(name: 'promo')]
#[ORM\Index(name: 'idx_promo_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_promo_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class Promo
{
use UpdateTimestampTrait;
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
#[Groups(['promo:read'])]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $name = null;
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?bool $active = null;
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?int $regionId = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $alias = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $anons = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
#[Groups(['promo:read'])]
private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?array $clinics = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $timer = null;
#[ORM\Column(name: 'timer_bg', type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $timerBg = null;
#[ORM\Column(name: 'short_name', type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $shortName = null;
#[ORM\Column(name: 'link_services', type: 'jsonb', nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?array $linkServices = null;
#[ORM\Column(name: 'link_staff', type: 'jsonb', nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?array $linkStaff = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?string $period = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['promo:read', 'promo:write'])]
private ?array $photos = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): self
{
$this->id = $id;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): self
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): self
{
$this->updateAt = $updateAt;
return $this;
}
public function getClinics(): ?array
{
return $this->clinics;
}
public function setClinics(?array $clinics): self
{
$this->clinics = $clinics;
return $this;
}
public function getTimer(): ?string
{
return $this->timer;
}
public function setTimer(?string $timer): self
{
$this->timer = $timer;
return $this;
}
public function getTimerBg(): ?string
{
return $this->timerBg;
}
public function setTimerBg(?string $timerBg): self
{
$this->timerBg = $timerBg;
return $this;
}
public function getShortName(): ?string
{
return $this->shortName;
}
public function setShortName(?string $shortName): self
{
$this->shortName = $shortName;
return $this;
}
public function getLinkServices(): ?array
{
return $this->linkServices;
}
public function setLinkServices(?array $linkServices): self
{
$this->linkServices = $linkServices;
return $this;
}
public function getLinkStaff(): ?array
{
return $this->linkStaff;
}
public function setLinkStaff(?array $linkStaff): self
{
$this->linkStaff = $linkStaff;
return $this;
}
public function getPeriod(): ?string
{
return $this->period;
}
public function setPeriod(?string $period): self
{
$this->period = $period;
return $this;
}
public function getPhotos(): ?array
{
return $this->photos;
}
public function setPhotos(?array $photos): self
{
$this->photos = $photos;
return $this;
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace App\Entity;
use App\Repository\RecordRepository;
use App\Bundle\Crypt\AES;
use Doctrine\ORM\Mapping as ORM;
use DateTimeInterface;
#[ORM\Entity(repositoryClass: RecordRepository::class)]
class Record
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'integer')]
private ?int $specialistId = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $phone = null;
#[ORM\Column(type: 'datetime')]
private ?DateTimeInterface $createAt = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $hash = null;
#[ORM\Column(type: 'json')]
private array $reserve = [];
#[ORM\OneToOne(targetEntity: AlertSms::class, mappedBy: 'record', cascade: ['persist', 'remove'])]
private ?AlertSms $alertSms = null;
public function getId(): ?int
{
return $this->id;
}
public function getSpecialistId(): ?int
{
return $this->specialistId;
}
public function setSpecialistId(int $specialistId): self
{
$this->specialistId = $specialistId;
return $this;
}
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(string $phone): self
{
$this->phone = $phone;
return $this;
}
public function getCreateAt(): ?DateTimeInterface
{
return $this->createAt;
}
public function setCreateAt(DateTimeInterface $createAt): self
{
$this->createAt = $createAt;
return $this;
}
public function getHash(): ?string
{
return $this->hash;
}
public function setHash(string $hash): self
{
$this->hash = md5($hash);
return $this;
}
public function getReserve(): array
{
return $this->reserve;
}
public function setReserve(array $reserve): self
{
$this->reserve = $reserve;
return $this;
}
public function getAlertSms(): ?AlertSms
{
return $this->alertSms;
}
public function setAlertSms(?AlertSms $alertSms): self
{
// unset the owning side of the relation if necessary
if ($alertSms === null && $this->alertSms !== null) {
$this->alertSms->setRecord(null);
}
// set the owning side of the relation if necessary
if ($alertSms !== null && $alertSms->getRecord() !== $this) {
$alertSms->setRecord($this);
}
$this->alertSms = $alertSms;
return $this;
}
}
+161
View File
@@ -0,0 +1,161 @@
<?php
namespace App\Entity;
use App\Entity\Specialist;
use App\Repository\ReviewRepository;
use Symfony\Component\Serializer\Annotation\Groups;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ReviewRepository::class)]
#[ORM\HasLifecycleCallbacks]
class Review
{
#[Groups(['from.specialist:read', 'review:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private $id;
#[Groups(['review:read'])]
#[ORM\ManyToOne(
targetEntity: Specialist::class,
inversedBy: 'reviews'
)]
#[ORM\JoinColumn(name: "specialist_id", referencedColumnName: "id")]
private Specialist $specialist;
#[Groups(['from.specialist:read', 'review:read', 'review:write'])]
#[ORM\Column(type: 'boolean')]
private $active;
#[Groups(['from.specialist:read', 'review:read'])]
#[ORM\Column(type: 'date')]
private $dateCreate;
#[Groups(['from.specialist:read', 'review:read', 'review:write'])]
#[ORM\Column(type: 'text')]
private $message;
#[Groups(['from.specialist:read', 'review:read', 'review:write'])]
#[ORM\Column(type: 'string', length: 255)]
private $author;
#[Groups(['from.specialist:read', 'review:read', 'review:write'])]
#[ORM\Column(type: 'float')]
private $rating;
#[Groups(['from.specialist:read', 'review:read', 'review:write'])]
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $source;
#[Groups(['review:read'])]
#[ORM\Column(nullable: true)]
private ?int $externalId = null;
public function getId(): ?int
{
return $this->id;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getDateCreate(): ?\DateTimeInterface
{
return $this->dateCreate;
}
public function setDateCreate(\DateTimeInterface $dateCreate): self
{
$this->dateCreate = $dateCreate;
return $this;
}
public function getMessage(): ?string
{
return $this->message;
}
public function setMessage(string $message): self
{
$this->message = $message;
return $this;
}
public function getAuthor(): ?string
{
return $this->author;
}
public function setAuthor(string $author): self
{
$this->author = $author;
return $this;
}
public function getRating(): ?float
{
return $this->rating;
}
public function setRating(float $rating): self
{
$this->rating = $rating;
return $this;
}
public function getSource(): ?string
{
return $this->source;
}
public function setSource(?string $source): self
{
$this->source = $source;
return $this;
}
public function isActive(): ?bool
{
return $this->active;
}
public function getSpecialist(): ?Specialist
{
return $this->specialist;
}
public function setSpecialist(?Specialist $specialist): static
{
$this->specialist = $specialist;
return $this;
}
public function getExternalId(): ?int
{
return $this->externalId;
}
public function setExternalId(?int $externalId): static
{
$this->externalId = $externalId;
return $this;
}
}
+249
View File
@@ -0,0 +1,249 @@
<?php
namespace App\Entity;
use App\Repository\ScheduleRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ScheduleRepository::class)]
#[ORM\Index(columns: ['query_string', 'created_at'], name: 'query_created_idx')]
#[ORM\Index(columns: ['department', 'workdate'], name: 'department_date_idx')]
#[ORM\Index(columns: ['online_mode'], name: 'online_mode_idx')]
class Schedule
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $dcode = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $department = null;
#[ORM\Column]
private ?int $filial = null;
#[ORM\Column(type: Types::BIGINT)]
private ?string $schedident = null;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $workdate = null;
#[ORM\Column(length: 255)]
private ?string $rnum = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $rfloor = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $rbuilding = null;
#[ORM\Column(length: 255)]
private ?string $time = null;
#[ORM\Column]
private ?bool $isFree = null;
#[ORM\Column]
private ?bool $onlineMode = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $queryString = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $createdAt = null;
#[ORM\Column]
private ?bool $intervalIsFree = null;
#[ORM\Column(type: Types::JSON, nullable: true)]
private ?array $priceInfo = null;
public function getId(): ?int
{
return $this->id;
}
public function getDcode(): ?string
{
return $this->dcode;
}
public function setDcode(string $dcode): static
{
$this->dcode = $dcode;
return $this;
}
public function getDepartment(): ?string
{
return $this->department;
}
public function setDepartment(string $department): static
{
$this->department = $department;
return $this;
}
public function getFilial(): ?int
{
return $this->filial;
}
public function setFilial(int $filial): static
{
$this->filial = $filial;
return $this;
}
public function getSchedident(): ?string
{
return $this->schedident;
}
public function setSchedident(string $schedident): static
{
$this->schedident = $schedident;
return $this;
}
public function getWorkdate(): ?\DateTimeInterface
{
return $this->workdate;
}
public function setWorkdate(\DateTimeInterface $workdate): static
{
$this->workdate = $workdate;
return $this;
}
public function getRnum(): ?string
{
return $this->rnum;
}
public function setRnum(string $rnum): static
{
$this->rnum = $rnum;
return $this;
}
public function getRfloor(): ?string
{
return $this->rfloor;
}
public function setRfloor(?string $rfloor): static
{
$this->rfloor = $rfloor;
return $this;
}
public function getRbuilding(): ?string
{
return $this->rbuilding;
}
public function setRbuilding(?string $rbuilding): static
{
$this->rbuilding = $rbuilding;
return $this;
}
public function getTime(): ?string
{
return $this->time;
}
public function setTime(string $time): static
{
$this->time = $time;
return $this;
}
public function isFree(): ?bool
{
return $this->isFree;
}
public function setIsFree(bool $isFree): static
{
$this->isFree = $isFree;
return $this;
}
public function isOnlineMode(): ?bool
{
return $this->onlineMode;
}
public function setOnlineMode(bool $onlineMode): static
{
$this->onlineMode = $onlineMode;
return $this;
}
public function getQueryString(): ?string
{
return $this->queryString;
}
public function setQueryString(?string $queryString): static
{
$this->queryString = $queryString;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(?\DateTimeInterface $createdAt): static
{
$this->createdAt = $createdAt;
return $this;
}
public function isIntervalIsFree(): ?bool
{
return $this->intervalIsFree;
}
public function setIntervalIsFree(bool $intervalIsFree): static
{
$this->intervalIsFree = $intervalIsFree;
return $this;
}
public function getPriceInfo(): ?array
{
return $this->priceInfo;
}
public function setPriceInfo(?array $priceInfo): static
{
$this->priceInfo = $priceInfo;
return $this;
}
}
+933
View File
@@ -0,0 +1,933 @@
<?php
namespace App\Entity;
use App\Entity\Behavior\UpdateTimestampTrait;
use App\Repository\SiteServiceRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: SiteServiceRepository::class)]
#[ORM\Table(name: 'site_services')]
#[ORM\Index(name: 'idx_site_services_region_id', columns: ['region_id'])]
#[ORM\Index(name: 'idx_site_services_active', columns: ['active'])]
#[ORM\HasLifecycleCallbacks]
class SiteService
{
use UpdateTimestampTrait;
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: Types::INTEGER)]
#[Groups(['site_service:read'])]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $name = null;
#[ORM\Column(type: Types::BOOLEAN, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?bool $active = null;
#[ORM\Column(name: 'region_id', type: Types::INTEGER, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?int $regionId = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $alias = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $anons = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $content = null;
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE, nullable: true)]
#[Groups(['site_service:read'])]
private ?\DateTimeInterface $updateAt = null;
#[ORM\Column(name: 'link_videoreviews', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $linkVideoreviews = null;
#[ORM\Column(name: 'preview_img', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $previewImg = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $faq = null;
#[ORM\Column(name: 'part_price', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $partPrice = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $pokazaniya = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $preparation = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $protivopokazaniya = null;
#[ORM\Column(name: 'hide_sign_btn', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $hideSignBtn = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $quiz = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $tags = null;
#[ORM\Column(name: 'tags_important', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $tagsImportant = null;
#[ORM\Column(name: 'banner_img', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $bannerImg = null;
#[ORM\Column(name: 'banner_img_m', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $bannerImgM = null;
#[ORM\Column(name: 'banner_img_url', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $bannerImgUrl = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $clinics = null;
#[ORM\Column(name: 'download_file', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $downloadFile = null;
#[ORM\Column(name: 'full_width_banner', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $fullWidthBanner = null;
#[ORM\Column(name: 'staff_up', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $staffUp = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $advantages = null;
#[ORM\Column(name: 'hide_picture', type: Types::INTEGER, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?int $hidePicture = null;
#[ORM\Column(name: 'kod_uslug', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $kodUslug = null;
#[ORM\Column(name: 'link_price', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $linkPrice = null;
#[ORM\Column(name: 'photos_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $photosTitle = null;
#[ORM\Column(name: 'sale_id', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $saleId = null;
#[ORM\Column(name: 'sort_staff', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $sortStaff = null;
#[ORM\Column(name: 'contraindications_list', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $contraindicationsList = null;
#[ORM\Column(name: 'custom_block_text', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $customBlockText = null;
#[ORM\Column(name: 'custom_block_text2', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $customBlockText2 = null;
#[ORM\Column(name: 'custom_block_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $customBlockTitle = null;
#[ORM\Column(name: 'custom_block_title2', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $customBlockTitle2 = null;
#[ORM\Column(name: 'indications_list', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $indicationsList = null;
#[ORM\Column(name: 'link_articles_services', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $linkArticlesServices = null;
#[ORM\Column(name: 'plus_list', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $plusList = null;
#[ORM\Column(name: 'plus_text', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $plusText = null;
#[ORM\Column(name: 'plus_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $plusTitle = null;
#[ORM\Column(name: 'prepare_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $prepareTitle = null;
#[ORM\Column(name: 'process_text', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $processText = null;
#[ORM\Column(name: 'process_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $processTitle = null;
#[ORM\Column(name: 'services_list', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $servicesList = null;
#[ORM\Column(name: 'services_photos', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $servicesPhotos = null;
#[ORM\Column(name: 'services_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $servicesTitle = null;
#[ORM\Column(name: 'text_up', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $textUp = null;
#[ORM\Column(name: 'training_text', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $trainingText = null;
#[ORM\Column(name: 'why_text', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $whyText = null;
#[ORM\Column(name: 'why_title', type: Types::TEXT, nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?string $whyTitle = null;
#[ORM\Column(name: 'link_faq', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $linkFaq = null;
#[ORM\Column(name: 'link_services', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $linkServices = null;
#[ORM\Column(name: 'link_staff', type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $linkStaff = null;
#[ORM\Column(type: 'jsonb', nullable: true)]
#[Groups(['site_service:read', 'site_service:write'])]
private ?array $photos = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): self
{
$this->id = $id;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(?string $name): self
{
$this->name = $name;
return $this;
}
public function getActive(): ?bool
{
return $this->active;
}
public function setActive(?bool $active): self
{
$this->active = $active;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): ?string
{
return $this->alias;
}
public function setAlias(?string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): self
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
public function getUpdateAt(): ?\DateTimeInterface
{
return $this->updateAt;
}
public function setUpdateAt(?\DateTimeInterface $updateAt): self
{
$this->updateAt = $updateAt;
return $this;
}
public function getLinkVideoreviews(): ?array
{
return $this->linkVideoreviews;
}
public function setLinkVideoreviews(?array $linkVideoreviews): self
{
$this->linkVideoreviews = $linkVideoreviews;
return $this;
}
public function getPreviewImg(): ?string
{
return $this->previewImg;
}
public function setPreviewImg(?string $previewImg): self
{
$this->previewImg = $previewImg;
return $this;
}
public function getFaq(): ?array
{
return $this->faq;
}
public function setFaq(?array $faq): self
{
$this->faq = $faq;
return $this;
}
public function getPartPrice(): ?string
{
return $this->partPrice;
}
public function setPartPrice(?string $partPrice): self
{
$this->partPrice = $partPrice;
return $this;
}
public function getPokazaniya(): ?string
{
return $this->pokazaniya;
}
public function setPokazaniya(?string $pokazaniya): self
{
$this->pokazaniya = $pokazaniya;
return $this;
}
public function getPreparation(): ?string
{
return $this->preparation;
}
public function setPreparation(?string $preparation): self
{
$this->preparation = $preparation;
return $this;
}
public function getProtivopokazaniya(): ?string
{
return $this->protivopokazaniya;
}
public function setProtivopokazaniya(?string $protivopokazaniya): self
{
$this->protivopokazaniya = $protivopokazaniya;
return $this;
}
public function getHideSignBtn(): ?array
{
return $this->hideSignBtn;
}
public function setHideSignBtn(?array $hideSignBtn): self
{
$this->hideSignBtn = $hideSignBtn;
return $this;
}
public function getQuiz(): ?array
{
return $this->quiz;
}
public function setQuiz(?array $quiz): self
{
$this->quiz = $quiz;
return $this;
}
public function getTags(): ?array
{
return $this->tags;
}
public function setTags(?array $tags): self
{
$this->tags = $tags;
return $this;
}
public function getTagsImportant(): ?array
{
return $this->tagsImportant;
}
public function setTagsImportant(?array $tagsImportant): self
{
$this->tagsImportant = $tagsImportant;
return $this;
}
public function getBannerImg(): ?string
{
return $this->bannerImg;
}
public function setBannerImg(?string $bannerImg): self
{
$this->bannerImg = $bannerImg;
return $this;
}
public function getBannerImgM(): ?string
{
return $this->bannerImgM;
}
public function setBannerImgM(?string $bannerImgM): self
{
$this->bannerImgM = $bannerImgM;
return $this;
}
public function getBannerImgUrl(): ?string
{
return $this->bannerImgUrl;
}
public function setBannerImgUrl(?string $bannerImgUrl): self
{
$this->bannerImgUrl = $bannerImgUrl;
return $this;
}
public function getClinics(): ?array
{
return $this->clinics;
}
public function setClinics(?array $clinics): self
{
$this->clinics = $clinics;
return $this;
}
public function getDownloadFile(): ?string
{
return $this->downloadFile;
}
public function setDownloadFile(?string $downloadFile): self
{
$this->downloadFile = $downloadFile;
return $this;
}
public function getFullWidthBanner(): ?string
{
return $this->fullWidthBanner;
}
public function setFullWidthBanner(?string $fullWidthBanner): self
{
$this->fullWidthBanner = $fullWidthBanner;
return $this;
}
public function getStaffUp(): ?array
{
return $this->staffUp;
}
public function setStaffUp(?array $staffUp): self
{
$this->staffUp = $staffUp;
return $this;
}
public function getAdvantages(): ?array
{
return $this->advantages;
}
public function setAdvantages(?array $advantages): self
{
$this->advantages = $advantages;
return $this;
}
public function getHidePicture(): ?int
{
return $this->hidePicture;
}
public function setHidePicture(?int $hidePicture): self
{
$this->hidePicture = $hidePicture;
return $this;
}
public function getKodUslug(): ?string
{
return $this->kodUslug;
}
public function setKodUslug(?string $kodUslug): self
{
$this->kodUslug = $kodUslug;
return $this;
}
public function getLinkPrice(): ?string
{
return $this->linkPrice;
}
public function setLinkPrice(?string $linkPrice): self
{
$this->linkPrice = $linkPrice;
return $this;
}
public function getPhotosTitle(): ?string
{
return $this->photosTitle;
}
public function setPhotosTitle(?string $photosTitle): self
{
$this->photosTitle = $photosTitle;
return $this;
}
public function getSaleId(): ?array
{
return $this->saleId;
}
public function setSaleId(?array $saleId): self
{
$this->saleId = $saleId;
return $this;
}
public function getSortStaff(): ?array
{
return $this->sortStaff;
}
public function setSortStaff(?array $sortStaff): self
{
$this->sortStaff = $sortStaff;
return $this;
}
public function getContraindicationsList(): ?string
{
return $this->contraindicationsList;
}
public function setContraindicationsList(?string $contraindicationsList): self
{
$this->contraindicationsList = $contraindicationsList;
return $this;
}
public function getCustomBlockText(): ?string
{
return $this->customBlockText;
}
public function setCustomBlockText(?string $customBlockText): self
{
$this->customBlockText = $customBlockText;
return $this;
}
public function getCustomBlockText2(): ?string
{
return $this->customBlockText2;
}
public function setCustomBlockText2(?string $customBlockText2): self
{
$this->customBlockText2 = $customBlockText2;
return $this;
}
public function getCustomBlockTitle(): ?string
{
return $this->customBlockTitle;
}
public function setCustomBlockTitle(?string $customBlockTitle): self
{
$this->customBlockTitle = $customBlockTitle;
return $this;
}
public function getCustomBlockTitle2(): ?string
{
return $this->customBlockTitle2;
}
public function setCustomBlockTitle2(?string $customBlockTitle2): self
{
$this->customBlockTitle2 = $customBlockTitle2;
return $this;
}
public function getIndicationsList(): ?string
{
return $this->indicationsList;
}
public function setIndicationsList(?string $indicationsList): self
{
$this->indicationsList = $indicationsList;
return $this;
}
public function getLinkArticlesServices(): ?array
{
return $this->linkArticlesServices;
}
public function setLinkArticlesServices(?array $linkArticlesServices): self
{
$this->linkArticlesServices = $linkArticlesServices;
return $this;
}
public function getPlusList(): ?string
{
return $this->plusList;
}
public function setPlusList(?string $plusList): self
{
$this->plusList = $plusList;
return $this;
}
public function getPlusText(): ?string
{
return $this->plusText;
}
public function setPlusText(?string $plusText): self
{
$this->plusText = $plusText;
return $this;
}
public function getPlusTitle(): ?string
{
return $this->plusTitle;
}
public function setPlusTitle(?string $plusTitle): self
{
$this->plusTitle = $plusTitle;
return $this;
}
public function getPrepareTitle(): ?string
{
return $this->prepareTitle;
}
public function setPrepareTitle(?string $prepareTitle): self
{
$this->prepareTitle = $prepareTitle;
return $this;
}
public function getProcessText(): ?string
{
return $this->processText;
}
public function setProcessText(?string $processText): self
{
$this->processText = $processText;
return $this;
}
public function getProcessTitle(): ?string
{
return $this->processTitle;
}
public function setProcessTitle(?string $processTitle): self
{
$this->processTitle = $processTitle;
return $this;
}
public function getServicesList(): ?string
{
return $this->servicesList;
}
public function setServicesList(?string $servicesList): self
{
$this->servicesList = $servicesList;
return $this;
}
public function getServicesPhotos(): ?array
{
return $this->servicesPhotos;
}
public function setServicesPhotos(?array $servicesPhotos): self
{
$this->servicesPhotos = $servicesPhotos;
return $this;
}
public function getServicesTitle(): ?string
{
return $this->servicesTitle;
}
public function setServicesTitle(?string $servicesTitle): self
{
$this->servicesTitle = $servicesTitle;
return $this;
}
public function getTextUp(): ?string
{
return $this->textUp;
}
public function setTextUp(?string $textUp): self
{
$this->textUp = $textUp;
return $this;
}
public function getTrainingText(): ?string
{
return $this->trainingText;
}
public function setTrainingText(?string $trainingText): self
{
$this->trainingText = $trainingText;
return $this;
}
public function getWhyText(): ?string
{
return $this->whyText;
}
public function setWhyText(?string $whyText): self
{
$this->whyText = $whyText;
return $this;
}
public function getWhyTitle(): ?string
{
return $this->whyTitle;
}
public function setWhyTitle(?string $whyTitle): self
{
$this->whyTitle = $whyTitle;
return $this;
}
public function getLinkFaq(): ?array
{
return $this->linkFaq;
}
public function setLinkFaq(?array $linkFaq): self
{
$this->linkFaq = $linkFaq;
return $this;
}
public function getLinkServices(): ?array
{
return $this->linkServices;
}
public function setLinkServices(?array $linkServices): self
{
$this->linkServices = $linkServices;
return $this;
}
public function getLinkStaff(): ?array
{
return $this->linkStaff;
}
public function setLinkStaff(?array $linkStaff): self
{
$this->linkStaff = $linkStaff;
return $this;
}
public function getPhotos(): ?array
{
return $this->photos;
}
public function setPhotos(?array $photos): self
{
$this->photos = $photos;
return $this;
}
}
+720
View File
@@ -0,0 +1,720 @@
<?php
namespace App\Entity;
use Doctrine\DBAL\Types\Types;
use App\Entity\Location;
use App\Entity\Review;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Repository\SpecialistRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Criteria;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation\SerializedName;
#[ORM\Entity(repositoryClass: SpecialistRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[ORM\Index(name: 'idx_specialist_s_type', columns: ['s_type'])]
#[ORM\Index(name: 'idx_specialist_alias', columns: ['alias'])]
#[ORM\Index(name: 'idx_specialist_region_id', columns: ['region_id'])]
class Specialist
{
#[Groups(['specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private int $id;
#[SerializedName('nameString')]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'string', length: 255)]
private string $name;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $previewPicture = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'boolean')]
private bool $active;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'boolean')]
private bool $displaySchedule;
#[ORM\Column(type: 'string', nullable: true)]
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
private ?string $dcodes = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $regionId = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'string', length: 255)]
private ?string $alias;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $post = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $experience = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $sType = null;
#[ORM\Column(type: 'datetime')]
private \DateTimeInterface $updateAt;
#[ORM\OneToMany(
targetEntity: Location::class,
mappedBy: 'specialist',
cascade: ['persist', 'remove'],
orphanRemoval: true
)]
#[Groups(['specialist:detail', 'specialist:read'])]
private Collection $locations;
#[ORM\OneToMany(
targetEntity: Review::class,
mappedBy: 'specialist',
cascade: ['persist', 'remove'],
orphanRemoval: true
)]
#[Groups(['specialist:detail'])]
private Collection $reviews;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $anons = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $content = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $tags = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $highlightedTags = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $video = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $videoVertical = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $scheduleText = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $category = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(nullable: true)]
private ?int $patientAge = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(type: 'jsonb', nullable: true)]
private ?array $kodoper = null;
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(nullable: true)]
private ?bool $onlyOnlineMode = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(nullable: true)]
private ?bool $prodoctor = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $prodoctorText = null;
#[Groups(['specialist:write', 'specialist:detail', 'to.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $prodoctorLink = null;
/**
* @var Collection<int, SpecialistDocs>
*/
#[Groups(['specialist:detail'])]
#[ORM\OneToMany(
targetEntity: SpecialistDocs::class,
mappedBy: 'specialist',
cascade: ['remove'],
orphanRemoval: true
)]
private Collection $specialistDocs;
/**
* @var Collection<int, Stock>
*/
#[Groups(['specialist:detail'])]
#[ORM\ManyToMany(targetEntity: Stock::class, mappedBy: 'specialist')]
private Collection $stocks;
#[ORM\Column(length: 255, nullable: true)]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
private ?string $degree = null;
#[ORM\Column]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
private ?bool $kiosk = null;
#[ORM\Column(type: Types::JSONB, nullable: true)]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
private mixed $filials = null;
#[ORM\Column(nullable: true)]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
private ?bool $acceptsDms = null;
#[ORM\Column(type: Types::JSONB, nullable: true)]
#[Groups(['specialist:write', 'specialist:read', 'specialist:detail', 'to.specialist:read'])]
private mixed $specialities = null;
public function __construct()
{
$this->locations = new ArrayCollection();
$this->specialistDocs = new ArrayCollection();
$this->stocks = new ArrayCollection();
$this->reviews = new ArrayCollection();
}
#[ORM\PreFlush]
public function preFlush()
{
$this->updateAt = new \DateTime();
}
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
#[SerializedName('fullName')]
#[Groups(['specialist:read', 'specialist:detail', 'to.specialist:read'])]
public function getFullName(): array
{
$fio = array_map('trim', explode(' ', trim($this->name)));
$result = [
'lastName' => '',
'firstName' => '',
'middleName' => ''
];
foreach ($fio as $index => $part) {
if ($index === 0) {
$result['lastName'] = $part;
} elseif ($index === 1) {
$result['firstName'] = $part;
} elseif ($index === 2) {
$result['middleName'] = $part;
break;
}
}
return $result;
}
public function getPreviewPicture(): ?string
{
return $this->previewPicture;
}
public function setPreviewPicture(?string $previewPicture): self
{
$this->previewPicture = $previewPicture;
return $this;
}
public function getActive(): bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getDisplaySchedule(): bool
{
return $this->displaySchedule;
}
public function setDisplaySchedule(bool $displaySchedule): self
{
$this->displaySchedule = $displaySchedule;
return $this;
}
public function getDcodes(): ?string
{
return $this->dcodes;
}
public function setDcodes(?string $dcodes): self
{
$this->dcodes = $dcodes;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getAlias(): string
{
return $this->alias;
}
public function setAlias(string $alias): self
{
$this->alias = $alias;
return $this;
}
public function getPost(): ?string
{
return $this->post;
}
public function setPost(?string $post): self
{
$this->post = $post;
return $this;
}
public function getExperience(): ?string
{
return $this->experience;
}
public function setExperience(?string $experience): self
{
$this->experience = $experience;
return $this;
}
public function getSType(): ?int
{
return $this->sType;
}
public function setSType(?int $sType): self
{
$this->sType = $sType;
return $this;
}
public function getUpdateAt(): \DateTimeInterface
{
return $this->updateAt;
}
public function getLocations(): Collection
{
return $this->locations;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): static
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): static
{
$this->content = $content;
return $this;
}
public function getTags(): ?array
{
return $this->tags;
}
public function setTags(?array $tags): static
{
$this->tags = $tags;
return $this;
}
public function getHighlightedTags(): ?array
{
return $this->highlightedTags;
}
public function setHighlightedTags(?array $highlightedTags): static
{
$this->highlightedTags = $highlightedTags;
return $this;
}
public function getVideo(): ?string
{
return $this->video;
}
public function setVideo(?string $video): static
{
$this->video = $video;
return $this;
}
public function getVideoVertical(): ?string
{
return $this->videoVertical;
}
public function setVideoVertical(?string $videoVertical): static
{
$this->videoVertical = $videoVertical;
return $this;
}
public function getScheduleText(): ?string
{
return $this->scheduleText;
}
public function setScheduleText(?string $scheduleText): static
{
$this->scheduleText = $scheduleText;
return $this;
}
public function getCategory(): ?string
{
return $this->category;
}
public function setCategory(?string $category): static
{
$this->category = $category;
return $this;
}
public function getPatientAge(): ?int
{
return $this->patientAge;
}
public function setPatientAge(?int $patientAge): static
{
$this->patientAge = $patientAge;
return $this;
}
public function getKodoper(): ?array
{
return $this->kodoper;
}
public function setKodoper(?array $kodoper): static
{
$this->kodoper = $kodoper;
return $this;
}
public function isOnlyOnlineMode(): ?bool
{
return $this->onlyOnlineMode;
}
public function setOnlyOnlineMode(?bool $onlyOnlineMode): static
{
$this->onlyOnlineMode = $onlyOnlineMode;
return $this;
}
public function isProdoctor(): ?bool
{
return $this->prodoctor;
}
public function setProdoctor(?bool $prodoctor): static
{
$this->prodoctor = $prodoctor;
return $this;
}
public function getProdoctorText(): ?string
{
return $this->prodoctorText;
}
public function setProdoctorText(?string $prodoctorText): static
{
$this->prodoctorText = $prodoctorText;
return $this;
}
public function getProdoctorLink(): ?string
{
return $this->prodoctorLink;
}
public function setProdoctorLink(?string $prodoctorLink): static
{
$this->prodoctorLink = $prodoctorLink;
return $this;
}
/**
* @return Collection<int, SpecialistDocs>
*/
public function getSpecialistDocs(): Collection
{
return $this->specialistDocs;
}
public function addSpecialistDoc(SpecialistDocs $specialistDoc): static
{
if (!$this->specialistDocs->contains($specialistDoc)) {
$this->specialistDocs->add($specialistDoc);
$specialistDoc->setSpecialist($this);
}
return $this;
}
public function removeSpecialistDoc(SpecialistDocs $specialistDoc): static
{
if ($this->specialistDocs->removeElement($specialistDoc)) {
// set the owning side to null (unless already changed)
if ($specialistDoc->getSpecialist() === $this) {
$specialistDoc->setSpecialist(null);
}
}
return $this;
}
/**
* @return Collection<int, Stock>
*/
public function getStocks(): Collection
{
return $this->stocks;
}
public function addStock(Stock $stock): static
{
if (!$this->stocks->contains($stock)) {
$this->stocks->add($stock);
$stock->addSpecialist($this);
}
return $this;
}
public function removeStock(Stock $stock): static
{
if ($this->stocks->removeElement($stock)) {
$stock->removeSpecialist($this);
}
return $this;
}
public function isActive(): ?bool
{
return $this->active;
}
public function isDisplaySchedule(): ?bool
{
return $this->displaySchedule;
}
public function setUpdateAt(\DateTime $updateAt): static
{
$this->updateAt = $updateAt;
return $this;
}
public function addLocation(Location $location): static
{
if (!$this->locations->contains($location)) {
$this->locations->add($location);
$location->setSpecialist($this);
}
return $this;
}
public function removeLocation(Location $location): static
{
if ($this->locations->removeElement($location)) {
// set the owning side to null (unless already changed)
if ($location->getSpecialist() === $this) {
$location->setSpecialist(null);
}
}
return $this;
}
/**
* @return Collection<int, Review>
*/
public function getReviews(): Collection
{
return $this->reviews;
}
public function addReview(Review $review): static
{
if (!$this->reviews->contains($review)) {
$this->reviews->add($review);
$review->setSpecialist($this);
}
return $this;
}
public function removeReview(Review $review): static
{
if ($this->reviews->removeElement($review)) {
// set the owning side to null (unless already changed)
if ($review->getSpecialist() === $this) {
$review->setSpecialist(null);
}
}
return $this;
}
#[SerializedName('reviewsCount')]
#[Groups(['specialist:read', 'specialist:detail', 'to.specialist:read'])]
public function getReviewsCount(): int
{
return count($this->reviews);
}
public function getDegree(): ?string
{
return $this->degree;
}
public function setDegree(?string $degree): static
{
$this->degree = $degree;
return $this;
}
public function isKiosk(): ?bool
{
return $this->kiosk;
}
public function setKiosk(bool $kiosk): static
{
$this->kiosk = $kiosk;
return $this;
}
public function getFilials(): mixed
{
return $this->filials;
}
public function setFilials(mixed $filials): static
{
$this->filials = $filials;
return $this;
}
public function isAcceptsDms(): ?bool
{
return $this->acceptsDms;
}
public function setAcceptsDms(?bool $acceptsDms): static
{
$this->acceptsDms = $acceptsDms;
return $this;
}
public function getSpecialities(): mixed
{
return $this->specialities;
}
public function setSpecialities(mixed $specialities): static
{
$this->specialities = $specialities;
return $this;
}
}
+121
View File
@@ -0,0 +1,121 @@
<?php
namespace App\Entity;
use App\Repository\SpecialistDcodeDescriptionRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: SpecialistDcodeDescriptionRepository::class)]
#[ORM\HasLifecycleCallbacks]
class SpecialistDcodeDescription
{
#[Groups(['specialist.dcode.description:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'IDENTITY')]
#[ORM\Column]
private ?int $id = null;
#[Groups(['specialist.dcode.description:read', 'specialist.dcode.description:write'])]
#[ORM\Column(type: 'bigint')]
private ?int $dcode = null;
#[Groups(['specialist.dcode.description:read', 'specialist.dcode.description:write'])]
#[ORM\Column(type: Types::BIGINT, nullable: true)]
private ?int $department = null;
#[Groups(['specialist.dcode.description:read'])]
#[ORM\ManyToOne]
#[ORM\JoinColumn(name: 'specialist_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private ?Specialist $specialist = null;
#[Groups(['specialist.dcode.description:read', 'specialist.dcode.description:write'])]
#[ORM\Column(type: Types::TEXT)]
private ?string $content = null;
#[Groups(['specialist.dcode.description:read'])]
#[ORM\Column(name: 'create_at', type: Types::DATETIME_IMMUTABLE)]
private ?\DateTimeImmutable $createAt = null;
#[Groups(['specialist.dcode.description:read'])]
#[ORM\Column(name: 'update_at', type: Types::DATETIME_IMMUTABLE)]
private ?\DateTimeImmutable $updateAt = null;
#[ORM\PrePersist]
public function onPrePersist(): void
{
$now = new \DateTimeImmutable();
$this->createAt = $now;
$this->updateAt = $now;
}
#[ORM\PreUpdate]
public function onPreUpdate(): void
{
$this->updateAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id;
}
public function getDcode(): ?int
{
return $this->dcode;
}
public function setDcode(?int $dcode): static
{
$this->dcode = $dcode;
return $this;
}
public function getDepartment(): ?int
{
return $this->department;
}
public function setDepartment(?int $department): static
{
$this->department = $department;
return $this;
}
public function getSpecialist(): ?Specialist
{
return $this->specialist;
}
public function setSpecialist(?Specialist $specialist): static
{
$this->specialist = $specialist;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): static
{
$this->content = $content;
return $this;
}
public function getCreateAt(): ?\DateTimeImmutable
{
return $this->createAt;
}
public function getUpdateAt(): ?\DateTimeImmutable
{
return $this->updateAt;
}
}
+122
View File
@@ -0,0 +1,122 @@
<?php
namespace App\Entity;
use App\Repository\SpecialistDocsRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: SpecialistDocsRepository::class)]
class SpecialistDocs
{
#[Groups(['from.specialist:read', 'specialist.docs:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column]
private ?int $id = null;
#[Groups(['from.specialist:read', 'specialist.docs:read', 'specialist.docs:write'])]
#[ORM\Column(length: 255)]
private ?string $name = null;
#[Groups(['from.specialist:read', 'specialist.docs:read', 'specialist.docs:write'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $description = null;
#[Groups(['from.specialist:read', 'specialist.docs:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $picture = null;
#[Assert\Type(type: 'boolean')]
#[Groups(['from.specialist:read', 'specialist.docs:read', 'specialist.docs:write'])]
#[ORM\Column]
private ?bool $active = null;
#[Groups(['from.specialist:read', 'specialist.docs:read', 'specialist.docs:write'])]
#[Assert\NotBlank()]
#[ORM\Column(length: 255)]
private ?string $type = null;
#[Groups(['specialist.docs:read'])]
#[ORM\ManyToOne(inversedBy: 'specialistDocs')]
private ?Specialist $specialist = null;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): static
{
$this->description = $description;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): static
{
$this->picture = $picture;
return $this;
}
public function isActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): static
{
$this->active = $active;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): static
{
$this->type = $type;
return $this;
}
public function getSpecialist(): ?Specialist
{
return $this->specialist;
}
public function setSpecialist(?Specialist $specialist): static
{
$this->specialist = $specialist;
return $this;
}
}
+158
View File
@@ -0,0 +1,158 @@
<?php
namespace App\Entity;
use App\Repository\StockRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: StockRepository::class)]
class Stock
{
#[Groups(['stock:read', 'from.specialist:read'])]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column]
private ?int $id = null;
#[Groups(['stock:write', 'stock:read', 'from.specialist:read'])]
#[ORM\Column(length: 255)]
private ?string $name = null;
#[Groups(['stock:write', 'stock:read', 'from.specialist:read'])]
#[ORM\Column(type: Types::TEXT)]
private ?string $content = null;
#[Groups(['stock:read', 'from.specialist:read'])]
#[ORM\Column(length: 255, nullable: true)]
private ?string $picture = null;
#[Groups(['stock:write', 'stock:read', 'from.specialist:read'])]
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $anons = null;
#[Groups(['stock:write', 'stock:read', 'from.specialist:read'])]
#[ORM\Column]
private ?\DateTime $startDate = null;
#[Groups(['stock:write', 'stock:read', 'from.specialist:read'])]
#[ORM\Column]
private ?\DateTime $endDate = null;
/**
* @var Collection<int, Specialist>
*/
#[Groups(['stock:read'])]
#[ORM\ManyToMany(targetEntity: Specialist::class, inversedBy: 'stocks')]
private Collection $specialist;
public function __construct()
{
$this->specialist = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): static
{
$this->content = $content;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): static
{
$this->picture = $picture;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): static
{
$this->anons = $anons;
return $this;
}
public function getStartDate(): ?\DateTime
{
return $this->startDate;
}
public function setStartDate(\DateTime $startDate): static
{
$this->startDate = $startDate;
return $this;
}
public function getEndDate(): ?\DateTime
{
return $this->endDate;
}
public function setEndDate(\DateTime $endDate): static
{
$this->endDate = $endDate;
return $this;
}
/**
* @return Collection<int, Specialist>
*/
public function getSpecialist(): Collection
{
return $this->specialist;
}
public function addSpecialist(Specialist $specialist): static
{
if (!$this->specialist->contains($specialist)) {
$this->specialist->add($specialist);
}
return $this;
}
public function removeSpecialist(Specialist $specialist): static
{
$this->specialist->removeElement($specialist);
return $this;
}
}
+192
View File
@@ -0,0 +1,192 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[UniqueEntity(fields: ['uid'], message: 'An account with the same uid, already exists.')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'integer', unique: true)]
private int $uid;
#[ORM\Column(type: 'string', length: 180, nullable: true)]
private ?string $email = null;
#[ORM\Column(type: 'json')]
private array $roles = [];
#[ORM\Column(type: 'integer')]
private int $regionId;
#[ORM\Column(type: 'string', length: 255)]
private string $password;
#[ORM\Column(type: Types::DATE_MUTABLE)]
private ?\DateTimeInterface $birthDate = null;
#[ORM\Column(type: 'datetime', nullable: true)]
private ?\DateTimeInterface $loggedIn = null;
public function toArray() : array
{
return [
'uid' => $this->getUid(),
'bdate' => $this->getBirthDate(),
'roles' => $this->getRoles(),
'regionId' => $this->getRegionId(),
'loggedIn' => $this->getLoggedIn()
];
}
public function getId(): int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials(): void
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getSalt(): ?string
{
return null;
}
public function getUsername(): string
{
return (string) $this->email;
}
public function getUid(): int
{
return $this->uid;
}
public function setUid(int $uid): self
{
$this->uid = $uid;
return $this;
}
public function getRegionId(): ?int
{
return $this->regionId;
}
public function setRegionId(?int $regionId): self
{
$this->regionId = $regionId;
return $this;
}
public function getBirthDate(): ?\DateTimeInterface
{
return $this->birthDate;
}
public function setBirthDate(\DateTimeInterface $birthDate): static
{
$this->birthDate = $birthDate;
return $this;
}
public function getLoggedIn(): ?\DateTimeInterface
{
return $this->loggedIn;
}
public function setLoggedIn(?\DateTimeInterface $loggedIn): static
{
$this->loggedIn = $loggedIn;
return $this;
}
public function updateLoggedIn(): static
{
$this->loggedIn = new \DateTime();
return $this;
}
/**
* @deprecated This method is kept for backward compatibility. The fullName field has been removed.
* @return null
*/
public function getFullName(): ?string
{
return null;
}
}
+284
View File
@@ -0,0 +1,284 @@
<?php
namespace App\Entity;
use App\Repository\WebGetDocinfoRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: WebGetDocinfoRepository::class)]
#[ORM\Table(name: 'web_get_docinfo')]
class WebGetDocinfo
{
#[Groups(['web_get_docinfo:read'])]
#[ORM\Id]
#[ORM\Column(type: 'bigint')]
private ?int $id = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Id]
#[ORM\Column(name: 'source_table', type: 'string', length: 10)]
private ?string $sourceTable = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'doc_name', type: 'string', length: 80, nullable: true)]
private ?string $docName = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'doc_post', type: 'string', length: 256, nullable: true)]
private ?string $docPost = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $filial = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'smallint', nullable: true)]
private ?int $viewinweb = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'bigint', nullable: true)]
private ?int $depnum = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'first_schid', type: 'bigint', nullable: true)]
private ?int $firstSchid = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'second_schid', type: 'bigint', nullable: true)]
private ?int $secondSchid = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'accepts_dms', type: 'integer', nullable: true)]
private ?int $acceptsDms = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'string', length: 1024, nullable: true)]
private ?string $anons = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'string', length: 4096, nullable: true)]
private ?string $content = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'datetime', nullable: true)]
private ?\DateTimeInterface $experience = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'smallint', nullable: true)]
private ?int $category = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(type: 'string', length: 120, nullable: true)]
private ?string $degree = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'patient_bage', type: 'integer', nullable: true)]
private ?int $patientBage = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'patient_fage', type: 'integer', nullable: true)]
private ?int $patientFage = null;
#[Groups(['web_get_docinfo:read'])]
#[ORM\Column(name: 'updated_at', type: 'datetime', nullable: true)]
private ?\DateTimeInterface $updatedAt = null;
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): self
{
$this->id = $id;
return $this;
}
public function getSourceTable(): ?string
{
return $this->sourceTable;
}
public function setSourceTable(?string $sourceTable): self
{
$this->sourceTable = $sourceTable;
return $this;
}
public function getDocName(): ?string
{
return $this->docName;
}
public function setDocName(?string $docName): self
{
$this->docName = $docName;
return $this;
}
public function getDocPost(): ?string
{
return $this->docPost;
}
public function setDocPost(?string $docPost): self
{
$this->docPost = $docPost;
return $this;
}
public function getFilial(): ?int
{
return $this->filial;
}
public function setFilial(?int $filial): self
{
$this->filial = $filial;
return $this;
}
public function getViewinweb(): ?int
{
return $this->viewinweb;
}
public function setViewinweb(?int $viewinweb): self
{
$this->viewinweb = $viewinweb;
return $this;
}
public function getDepnum(): ?int
{
return $this->depnum;
}
public function setDepnum(?int $depnum): self
{
$this->depnum = $depnum;
return $this;
}
public function getFirstSchid(): ?int
{
return $this->firstSchid;
}
public function setFirstSchid(?int $firstSchid): self
{
$this->firstSchid = $firstSchid;
return $this;
}
public function getSecondSchid(): ?int
{
return $this->secondSchid;
}
public function setSecondSchid(?int $secondSchid): self
{
$this->secondSchid = $secondSchid;
return $this;
}
public function getAcceptsDms(): ?int
{
return $this->acceptsDms;
}
public function setAcceptsDms(?int $acceptsDms): self
{
$this->acceptsDms = $acceptsDms;
return $this;
}
public function getAnons(): ?string
{
return $this->anons;
}
public function setAnons(?string $anons): self
{
$this->anons = $anons;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): self
{
$this->content = $content;
return $this;
}
public function getExperience(): ?\DateTimeInterface
{
return $this->experience;
}
public function setExperience(?\DateTimeInterface $experience): self
{
$this->experience = $experience;
return $this;
}
public function getCategory(): ?int
{
return $this->category;
}
public function setCategory(?int $category): self
{
$this->category = $category;
return $this;
}
public function getDegree(): ?string
{
return $this->degree;
}
public function setDegree(?string $degree): self
{
$this->degree = $degree;
return $this;
}
public function getPatientBage(): ?int
{
return $this->patientBage;
}
public function setPatientBage(?int $patientBage): self
{
$this->patientBage = $patientBage;
return $this;
}
public function getPatientFage(): ?int
{
return $this->patientFage;
}
public function setPatientFage(?int $patientFage): self
{
$this->patientFage = $patientFage;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
+83
View File
@@ -0,0 +1,83 @@
<?php
namespace App\Entity;
use App\Repository\WidgetFormRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: WidgetFormRepository::class)]
class WidgetForm
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $name = null;
#[ORM\OneToMany(
targetEntity: WidgetFormInput::class,
mappedBy: 'widgetForm',
cascade: ['persist', 'remove'],
orphanRemoval: true
)]
private Collection $widgetFormInputs;
public function __construct()
{
$this->widgetFormInputs = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return Collection<WidgetFormInput>
*/
public function getWidgetFormInputs(): Collection
{
$criteria = Criteria::create()
->orderBy(['sort' => Criteria::ASC]);
return $this->widgetFormInputs->matching($criteria);
}
public function addWidgetFormInput(WidgetFormInput $widgetFormInput): self
{
if (!$this->widgetFormInputs->contains($widgetFormInput)) {
$this->widgetFormInputs->add($widgetFormInput);
$widgetFormInput->setWidgetForm($this);
}
return $this;
}
public function removeWidgetFormInput(WidgetFormInput $widgetFormInput): self
{
if ($this->widgetFormInputs->removeElement($widgetFormInput)) {
if ($widgetFormInput->getWidgetForm() === $this) {
$widgetFormInput->setWidgetForm(null);
}
}
return $this;
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Entity;
use App\Repository\WidgetFormInputRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: WidgetFormInputRepository::class)]
class WidgetFormInput
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: "IDENTITY")]
#[ORM\Column(type: 'integer')]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $text = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $type = null;
#[ORM\Column(type: 'string', length: 255)]
private ?string $bitrix24Id = null;
#[ORM\ManyToOne(targetEntity: WidgetForm::class, inversedBy: 'widgetFormInputs')]
#[ORM\JoinColumn(nullable: false)]
private ?WidgetForm $widgetForm = null;
#[ORM\Column(type: 'integer')]
private ?int $sort = null;
public function getId(): ?int
{
return $this->id;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
public function getBitrix24Id(): ?string
{
return $this->bitrix24Id;
}
public function setBitrix24Id(string $bitrix24Id): self
{
$this->bitrix24Id = $bitrix24Id;
return $this;
}
public function getWidgetForm(): ?WidgetForm
{
return $this->widgetForm;
}
public function setWidgetForm(?WidgetForm $widgetForm): self
{
$this->widgetForm = $widgetForm;
return $this;
}
public function getSort(): ?int
{
return $this->sort;
}
public function setSort(int $sort): self
{
$this->sort = $sort;
return $this;
}
}
@@ -0,0 +1,41 @@
<?php
namespace App\EventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
class JsonExceptionHandler
{
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
// Обрабатываем только определенные типы исключений
if (!$exception instanceof NotFoundHttpException &&
!$exception instanceof BadRequestHttpException) {
return;
}
// Определяем тип ошибки и создаем соответствующий ответ
if ($exception instanceof NotFoundHttpException) {
$response = new JsonResponse([
'error' => 'Not Found',
'message' => 'The requested resource was not found',
'code' => 404,
'status' => 'error'
], 404);
} elseif ($exception instanceof BadRequestHttpException) {
$response = new JsonResponse([
'error' => 'Bad Request',
'message' => $exception->getMessage() ?: 'Invalid request parameters',
'code' => 400,
'status' => 'error'
], 400);
}
$event->setResponse($response);
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App\Form;
use App\Entity\WidgetFormInput;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class WidgetFormInputType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('text')
->add('type', ChoiceType::class, [
'choices' => [
'Строка' => 'text',
'Телефон' => 'phone',
'Календаль' => 'date',
'Коментарий' => 'textarea',
],
'empty_data' => null,
'required' => false
])
->add('bitrix24Id')
->add('sort')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => WidgetFormInput::class,
]);
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Form;
use App\Entity\WidgetForm;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class WidgetFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => WidgetForm::class,
]);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}
@@ -0,0 +1,19 @@
<?php
namespace App\Message;
use App\Dto\AnonymousReserveRequestDto;
use Symfony\Component\Messenger\Attribute\AsMessage;
#[AsMessage('scheduler_default')]
final class GetAnonymousReserveRequestMessage
{
public function __construct(
private AnonymousReserveRequestDto $dto
) { }
public function getDto(): AnonymousReserveRequestDto
{
return $this->dto;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Message;
#[AsMessage('scheduler_default')]
class GetScheduleMessage
{
private string $queryString;
private bool $isOnlineMode;
public function __construct(string $queryString, bool $isOnlineMode)
{
$this->queryString = $queryString;
$this->isOnlineMode = $isOnlineMode;
}
public function getQueryString(): string
{
return $this->queryString;
}
public function isOnlineMode(): bool
{
return $this->isOnlineMode;
}
public function getMessageKey(): string
{
return sprintf('%s_%s', $this->queryString, $this->isOnlineMode ? 'online' : 'offline');
}
}
@@ -0,0 +1,18 @@
<?php
namespace App\Message;
use Symfony\Component\Messenger\Attribute\AsMessage;
#[AsMessage('scheduler_default')]
final class GetSpecialistPictureMessage
{
public function __construct(
private int $spesialistId
) { }
public function getSpesialistId(): int
{
return $this->spesialistId;
}
}
@@ -0,0 +1,75 @@
<?php
namespace App\MessageHandler;
use App\Message\GetAnonymousReserveRequestMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use App\Service\Client\InfoclinicaClientService;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
#[AsMessageHandler]
final class GetAnonymousReserveRequestMessageHandler
{
private ?int $startTime = null;
public function __construct(
private LoggerInterface $logger,
private InfoclinicaClientService $clientService
) { }
private function getDuration(): int
{
if (!$this->startTime) {
return 0;
}
return (int)round((microtime(true) - $this->startTime) * 1000);
}
public function __invoke(GetAnonymousReserveRequestMessage $message): array
{
$this->logger->info('[GetAnonymousReserve] Starting processing request', [
'query' => $message->getDto(),
'message_class' => GetAnonymousReserveRequestMessage::class
]);
try {
$startTime = microtime(true);
$result = $this->clientService->anonymousReserve($message->getDto());
$this->logger->info('[GetAnonymousReserve] Successfully processed request', [
'query' => $message->getDto(),
'duration_ms' => $this->getDuration(),
'result_count' => count($result)
]);
return $result;
} catch (HttpExceptionInterface $e) {
$exception = [
'query' => $message->getDto(),
'duration_ms' => $this->getDuration(),
'status_code' => $e->getResponse()->getStatusCode(),
'error' => $e->getMessage(),
'response' => $e->getResponse()->getContent(false)
];
$this->logger->error('[GetAnonymousReserve] API request failed', $exception);
return $exception;
} catch (\Exception $e) {
$exception = [
'query' => $message->getDto(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
];
$this->logger->critical('[GetAnonymousReserve] Unexpected error', $exception);
return $exception;
}
}
}
@@ -0,0 +1,95 @@
<?php
namespace App\MessageHandler;
use App\Message\GetScheduleMessage;
use App\Service\Client\InfoclinicaClientService;
use App\Service\ScheduleCache\ScheduleCacheService;
use App\Service\ErrorHandler\ScheduleErrorHandlerService;
use App\Service\Performance\PerformanceTrackerService;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
#[AsMessageHandler]
final class GetScheduleMessageHandler
{
public function __construct(
private InfoclinicaClientService $clientService,
private ScheduleCacheService $cacheService,
private ScheduleErrorHandlerService $errorHandler,
private PerformanceTrackerService $performanceTracker
) {
}
public function __invoke(GetScheduleMessage $message): array
{
$this->performanceTracker->start();
$queryString = $message->getQueryString();
$isOnlineMode = $message->isOnlineMode();
try {
// Проверяем кэш
$cachedResult = $this->cacheService->getCachedSchedule($queryString, $isOnlineMode);
if ($cachedResult !== null) {
$this->performanceTracker->stop();
return $this->createSuccessResponse($cachedResult, 'cached', $isOnlineMode);
}
// Получаем данные из API
// Передаем флаг onlineMode в клиент для формирования правильного запроса
$apiResult = $this->clientService->getSchedule($queryString, $isOnlineMode);
// Сохраняем в кэш
$this->cacheService->saveSchedule($apiResult, $queryString, $isOnlineMode);
$this->performanceTracker->stop();
return $this->createSuccessResponse($apiResult, 'api', $isOnlineMode);
} catch (HttpExceptionInterface $e) {
$this->performanceTracker->stop();
return $this->errorHandler->handleHttpException(
$e,
$queryString,
$this->performanceTracker->getDurationMs(),
$isOnlineMode
);
} catch (\Exception $e) {
$this->performanceTracker->stop();
return $this->errorHandler->handleGeneralException(
$e,
$queryString,
$this->performanceTracker->getDurationMs(),
$isOnlineMode
);
}
}
private function createSuccessResponse(array $data, string $source, bool $isOnlineMode): array
{
// Подсчет статистики
$totalIntervals = 0;
$totalDays = 0;
$totalDepartments = 0;
foreach ($data['schedule'] ?? [] as $department => $dates) {
$totalDepartments++;
foreach ($dates as $daySchedule) {
$totalDays++;
$totalIntervals += count($daySchedule['intervals'] ?? []);
}
}
return array_merge($data, [
'_meta' => [
'source' => $source,
'online_mode' => $isOnlineMode,
'duration_ms' => $this->performanceTracker->getDurationMs(),
'departments_count' => $totalDepartments,
'days_count' => $totalDays,
'intervals_count' => $totalIntervals,
'timestamp' => (new \DateTime())->format('c')
]
]);
}
}
@@ -0,0 +1,102 @@
<?php
namespace App\MessageHandler;
use App\Message\GetSpecialistPictureMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use App\Service\Client\BitrixClientService;
use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\FileUploader\Interfaces\FileUploaderServiceInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Entity\Specialist;
#[AsMessageHandler]
final class GetSpecialistPictureMessageHandler
{
public function __construct(
private LoggerInterface $logger,
private BitrixClientService $bitixClient,
private FileUploaderServiceInterface $fileUploaderService,
private EntityManagerInterface $em
) {
$this->logger = $logger->withName('bitrix');
}
public function __invoke(GetSpecialistPictureMessage $message): string
{
$specialist = $this->em->getRepository(Specialist::class)
->find($message->getSpesialistId());
$urlPicture = $specialist->getPreviewPicture();
if (!str_starts_with($urlPicture, '/upload/iblock/'))
return $urlPicture;
$this->logger->info('[GetSpecialistPictureMessage] Starting processing request', [
'specialist_id' => $specialist->getId(),
'preview_picture' => $specialist->getPreviewPicture(),
'message_class' => GetSpecialistPictureMessage::class
]);
try {
$startTime = microtime(true);
// Получаем содержимое изображения
$fileContent = $this->bitixClient->getSpecialistImage($urlPicture);
// Создаем временный файл
$tempFilePath = sys_get_temp_dir() . '/' . uniqid('img_', true);
file_put_contents($tempFilePath, $fileContent);
$uploadedFile = new UploadedFile(
$tempFilePath,
basename($urlPicture),
mime_content_type($tempFilePath),
null,
true
);
$this->fileUploaderService->setTargetDirectory('specialist');
$fileName = $this->fileUploaderService->upload($uploadedFile);
$duration = round((microtime(true) - $startTime) * 1000, 2);
$this->logger->info('[GetSpecialistPictureMessage] Successfully processed request', [
'specialist_id' => $specialist->getId(),
'duration_ms' => $duration,
'file_name' => $fileName
]);
$specialist->setPreviewPicture('specialist/'. $fileName);
$this->em->persist($specialist);
$this->em->flush();
$this->em->clear();
@unlink($tempFilePath);
return 'specialist/' . $fileName;
} catch (HttpExceptionInterface $e) {
$this->logger->error('[GetSpecialistPictureMessage] API request failed', [
'specialist_id' => $specialist->getId(),
'preview_picture' => $specialist->getPreviewPicture(),
'status_code' => $e->getResponse()->getStatusCode(),
'error' => $e->getMessage(),
'response' => $e->getResponse()->getContent(false)
]);
throw $e;
} catch (\Exception $e) {
$this->logger->critical('[GetSpecialistPictureMessage] Unexpected error', [
'specialist_id' => $specialist->getId(),
'preview_picture' => $specialist->getPreviewPicture(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
throw $e;
}
}
}
View File
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Repository;
use App\Entity\AlertSms;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method AlertSms|null find($id, $lockMode = null, $lockVersion = null)
* @method AlertSms|null findOneBy(array $criteria, array $orderBy = null)
* @method AlertSms[] findAll()
* @method AlertSms[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class AlertSmsRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, AlertSms::class);
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Article>
*/
class ArticleRepository extends ServiceEntityRepository
{
use ContentFilterTrait;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Article::class);
}
/**
*/
public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{
$qb = $this->createQueryBuilder('a')->orderBy('a.id', 'DESC');
$this->applyCommonFilters($qb, 'a', $filters);
return $qb;
}
/**
* Поиск статьи по alias с учётом возможных вариантов написания (исторический функционал).
*/
public function findOneByAlias(string $alias): ?Article
{
$alias = trim($alias);
if ($alias === '') {
return null;
}
$variants = [
$alias,
$alias . '-',
'/' . ltrim($alias, '/'),
];
foreach ($variants as $v) {
$article = $this->findOneBy(['alias' => $v]);
if ($article !== null) {
return $article;
}
}
// Фолбэк по TRIM(alias) в БД для совместимости со старыми данными.
$conn = $this->getEntityManager()->getConnection();
$id = $conn->fetchOne(
'SELECT id FROM article WHERE TRIM(alias) = :alias LIMIT 1',
['alias' => $alias],
['alias' => \PDO::PARAM_STR],
);
if ($id !== false) {
return $this->find($id);
}
return null;
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Repository;
use App\Entity\Banner;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Banner|null find($id, $lockMode = null, $lockVersion = null)
* @method Banner|null findOneBy(array $criteria, array $orderBy = null)
* @method Banner[] findAll()
* @method Banner[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class BannerRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Banner::class);
}
}
+58
View File
@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use Doctrine\ORM\QueryBuilder;
/**
* Общие фильтры для контентных репозиториев (News/Promo/Disease/MedicalCenter/Article/SiteService).
*
* Trait подключается в Doctrine-репозитории, чтобы не держать бизнес-фильтры
* в статическом helper-классе и при этом не копировать одинаковые if-блоки.
*
* Поддерживается:
* - regionId / region_id: целое > 0;
* - active: bool;
* - alias: точное совпадение;
* - search / q: LIKE по lower-case значению заданного поля (по умолчанию `name`).
*
* Поле поиска параметризовано через $searchField на случай сущностей,
* где основное текстовое поле называется иначе (например, `title`).
* Если у сущности нет такого свойства, Doctrine упадёт с QueryException — это
* лучше ловится тестами на этапе разработки, чем 500 в проде.
*
* Важно: LOWER($alias.$searchField) при больших таблицах требует функционального
* индекса в PostgreSQL, например CREATE INDEX ... ON table (LOWER(name)).
*/
trait ContentFilterTrait
{
private function applyCommonFilters(
QueryBuilder $qb,
string $alias,
ContentFilterDto $filters,
string $searchField = 'name',
): void {
if ($filters->regionId !== null) {
$qb->andWhere("$alias.regionId = :regionId")
->setParameter('regionId', $filters->regionId);
}
if ($filters->active !== null) {
$qb->andWhere("$alias.active = :active")
->setParameter('active', $filters->active);
}
if ($filters->alias !== null) {
$qb->andWhere("$alias.alias = :aliasValue")
->setParameter('aliasValue', $filters->alias);
}
if ($filters->search !== null) {
$qb->andWhere("LOWER($alias.$searchField) LIKE :search")
->setParameter('search', '%' . mb_strtolower($filters->search) . '%');
}
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace App\Repository;
use App\Entity\Department;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\QueryBuilder;
/**
* @method Department|null find($id, $lockMode = null, $lockVersion = null)
* @method Department|null findOneBy(array $criteria, array $orderBy = null)
* @method Department[] findAll()
* @method Department[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class DepartmentRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Department::class);
}
public function activeAll(): array
{
return $this->createQueryBuilder('d')
->where('d.active = :active')
->setParameter('active', true)
->orderBy('d.did', 'ASC')
->getQuery()
->getResult();
}
public function createFilteredQueryBuilder(array $filters): QueryBuilder
{
$qb = $this->createQueryBuilder('d')
->orderBy('d.id', 'ASC');
foreach ($filters as $filterType => $filterValue) {
if ($filterValue === null) {
continue;
}
match ($filterType) {
'id' => $this->applyDidFilter($qb, $filterValue),
'name' => $this->applyNameFilter($qb, $filterValue),
'alias' => $this->applyAliasFilter($qb, $filterValue),
'active' => $this->applyActiveFilter($qb, $filterValue),
'groupName' => $this->applyGroupNameFilter($qb, $filterValue),
default => null
};
}
return $qb;
}
private function applyDidFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.did in (:did)')
->setParameter('did', $value);
}
private function applyNameFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere($qb->expr()->orX(
$qb->expr()->like('LOWER(d.name)', 'LOWER(:name)')
))
->setParameter('name', '%'.$value.'%');
}
private function applyAliasFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.alias = :alias')
->setParameter('alias', $value);
}
private function applyActiveFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.active = :active')
->setParameter('active', $value);
}
private function applyGroupNameFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.groupName = :groupName')
->setParameter('groupName', $value);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Repository;
use App\Dto\Content\ContentFilterDto;
use App\Entity\Disease;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method Disease|null find($id, $lockMode = null, $lockVersion = null)
* @method Disease|null findOneBy(array $criteria, array $orderBy = null)
* @method Disease[] findAll()
* @method Disease[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class DiseaseRepository extends ServiceEntityRepository
{
use ContentFilterTrait;
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Disease::class);
}
/**
*/
public function createFilteredQueryBuilder(ContentFilterDto $filters): QueryBuilder
{
$qb = $this->createQueryBuilder('d')->orderBy('d.id', 'ASC');
$this->applyCommonFilters($qb, 'd', $filters);
return $qb;
}
}
+70
View File
@@ -0,0 +1,70 @@
<?php
namespace App\Repository;
use App\Entity\Filial;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\QueryBuilder;
/**
* @method Filial|null find($id, $lockMode = null, $lockVersion = null)
* @method Filial|null findOneBy(array $criteria, array $orderBy = null)
* @method Filial[] findAll()
* @method Filial[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class FilialRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Filial::class);
}
public function createFilteredQueryBuilder(array $filters): QueryBuilder
{
$qb = $this->createQueryBuilder('f')
->orderBy('f.id', 'ASC');
foreach ($filters as $filterType => $filterValue) {
if ($filterValue === null) {
continue;
}
match ($filterType) {
'name' => $this->applyNameFilter($qb, $filterValue),
'address' => $this->applyAddressFilter($qb, $filterValue),
'active' => $this->applyActiveFilter($qb, $filterValue),
'regionId' => $this->applyRegionIdFilter($qb, $filterValue),
default => null
};
}
return $qb;
}
private function applyNameFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere($qb->expr()->orX(
$qb->expr()->like('LOWER(f.name)', 'LOWER(:name)')
))
->setParameter('name', '%'.$value.'%');
}
private function applyAddressFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('f.address = :address')
->setParameter('address', $value);
}
private function applyActiveFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('f.active = :active')
->setParameter('active', $value);
}
private function applyRegionIdFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('f.regionId = :regionId')
->setParameter('regionId', $value);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Repository;
use App\Entity\Idoctor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\ORM\QueryBuilder;
/**
* @extends ServiceEntityRepository<Idoctor>
*/
class IdoctorRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Idoctor::class);
}
public function createFilteredQueryBuilder(array $filters): QueryBuilder
{
$qb = $this->createQueryBuilder('d')
->orderBy('d.id', 'ASC');
foreach ($filters as $filterType => $filterValue) {
if ($filterValue === null) {
continue;
}
match ($filterType) {
'filial' => $this->applyFilialFilter($qb, $filterValue),
'department' => $this->applyDepartmentFilter($qb, $filterValue),
'dcode' => $this->applyDcodeFilter($qb, $filterValue),
'search' => $this->applySearchFilter($qb, $filterValue),
default => null
};
}
return $qb;
}
private function applyFilialFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.filial = :filial')
->setParameter('filial', $value);
}
private function applyDepartmentFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.department = :department')
->setParameter('department', $value);
}
private function applyDcodeFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere('d.dcode = :dcode')
->setParameter('dcode', $value);
}
private function applySearchFilter(QueryBuilder $qb, mixed $value): void
{
$qb->andWhere($qb->expr()->orX(
$qb->expr()->like('LOWER(d.name)', 'LOWER(:search)')
))
->setParameter('search', '%'.$value.'%');
}
}

Some files were not shown because too many files have changed in this diff Show More