Files
cabinet/src/Controller/PublicAPIController.php
T
2026-05-28 12:09:28 +03:00

689 lines
24 KiB
PHP

<?php
namespace App\Controller;
use App\Bundle\Infoclinica\Region;
use App\Support\OnlineMode;
use App\Entity\Record;
use App\Entity\PriceDepartment;
use App\Entity\PriceList;
use App\Entity\Filial;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use App\Bundle\Utils\Logger;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpClient\CachingHttpClient;
use Symfony\Component\HttpKernel\HttpCache\Store;
use OpenApi\Annotations as OA;
use App\Service\SpecialistService;
use App\Service\PriceListService;
use Knp\Component\Pager\PaginatorInterface;
/**
* @OA\Info(title="Open API sovamed",
* description="Справочник методов доступных в Open API sovamed.",
* version="3.0.0"
* )
*
* @OA\Server( url="https://dev.sovamed.ru/api", description="Open API sovamed")
*
* @Route("/api")
*/
class PublicAPIController extends AbstractController
{
private $client;
public function __construct(HttpClientInterface $client, string $rootPath)
{
$store = new Store($rootPath . '/var/HttpClient');
$this->client = new CachingHttpClient($client, $store);
}
/**
* @Route("/anonymous-reserve", methods={"POST"})
*/
public function anonymousReserve(Request $request): Response
{
try {
$timezone = Region::getTimezone();
if (!empty($request->request->get('timezone'))) {
$timezone = (int) $request->request->get('timezone');
}
$reserve = [
'date' => date('Ymd', strtotime($request->request->get('workDate'))),
'st' => explode('-', $request->request->get('time'))[0],
'en' => explode('-', $request->request->get('time'))[1],
'services' => [],
'filial' => (int) $request->request->get('filial'),
'timezone' => $timezone,
'schedident' => (int) $request->request->get('schedident'),
'rnum' => $request->request->get('rnum') === 'undefined' ? null : $request->request->get('rnum'),
'dcode' => (int) $request->request->get('specialist')
];
$requestData = [
'accept' => 'true',
'fio' => $request->request->get('fio'),
'captcha' => $request->request->get('captcha'),
'email' => $request->request->get('email'),
'phone' => $request->request->get('phone'),
'reserve' => json_encode($reserve, JSON_UNESCAPED_SLASHES)
];
$referer = $request->headers->get('referer');
$response = $this->client->request('POST', '/api/reservation/anonymous-reserve', [
'verify_peer' => false,
'verify_host' => false,
'base_uri' => $_ENV['MIS'],
'headers' => [
'Referer' => $referer,
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0',
'Accept-Language' => 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
'Accept' => 'application/json, text/javascript, */*; q=0.01',
'Content-Type' => 'application/json; charset=UTF-8',
'X-Requested-With' => 'XMLHttpRequest',
'X-Integration-Type' => 'WEBSDK'
],
'body' => json_encode($requestData)
]);
// Проверяем статус ответа
$statusCode = $response->getStatusCode();
if ($statusCode !== 200) {
throw new \Exception("External API returned status: {$statusCode}");
}
$intervals = $response->toArray();
// Сохраняем запись
$entityManager = $this->getDoctrine()->getManager();
$record = new Record();
$record
->setSpecialistId((int) $request->request->get('specialist'))
->setPhone($request->request->get('phone'))
->setHash($request->request->get('phone'))
->setReserve($reserve)
->setCreateAt(new \DateTime('NOW'));
$entityManager->persist($record);
$entityManager->flush();
return $this->json([
'success' => true,
'data' => [
'intervals' => $intervals,
'hash' => md5($request->request->get('phone')),
'phone' => $request->request->get('phone'),
'recordId' => $record->getId(),
]
]);
} catch (\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface $e) {
// Ошибка 4xx
return $this->json([
'success' => false,
'error' => 'Client error: ' . $e->getMessage()
], 400);
} catch (\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface $e) {
// Ошибка 5xx
return $this->json([
'success' => false,
'error' => 'Server error: ' . $e->getMessage()
], 502);
} catch (\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface $e) {
// Ошибки сети
return $this->json([
'success' => false,
'error' => 'Network error: ' . $e->getMessage()
], 503);
} catch (\Exception $e) {
// Другие ошибки
return $this->json([
'success' => false,
'error' => 'Internal error: ' . $e->getMessage()
], 500);
}
}
/**
* @OA\Get(
* tags= {"Расписание врача"},
* path="/interval",
* summary="Получение сетки расписания",
* @OA\Parameter(
* name="startInterval",
* description="Начальная дата (Y-m-d)",
* in="query",
* required=true,
* @OA\Schema(
* type="string",
* format="Y-m-d"
*
* )
* ),
* @OA\Parameter(
* name="endInterval",
* description="Конечна дата (Y-m-d)",
* in="query",
* required=true,
* @OA\Schema(
* type="string",
* format="Y-m-d"
*
* )
* ),
* @OA\Parameter(
* name="department",
* description="ID отделения",
* in="query",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="doctor",
* description="ID врача",
* in="query",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="filial",
* description="ID филиала",
* in="query",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(
* response=200,
* description="json response"
* )
* )
*
* @Route("/interval", methods={"GET"})
*/
public function interval(Request $request): Response
{
$dateFormat = $request->query->get('dateFormat');
if (empty($dateFormat)) {
$dateFormat = 'Y-m-d';
}
$startInterval = $request->query->get('startInterval');
$endInterval = $request->query->get('endInterval');
$doctor = $request->query->get('doctor');
$department = $request->query->get('department');
$filial = $request->query->get('filial');
$onlineMode = OnlineMode::isOnline($request->query->get('onlineMode'));
$isFree = true;
$nearestDate = NULL;
if (empty($doctor) || empty($startInterval) || empty($endInterval) || empty($department) || empty($filial)) {
throw new BadRequestHttpException('Bad request');
}
$schedules = $this->getSchedule($doctor, $department, $filial, $onlineMode, $startInterval, $endInterval);
$intervals = $this->getInterval($doctor, $department, $filial, $onlineMode, $startInterval, $endInterval);
$findInterval = function ($schedident, $workDate) use($intervals, $onlineMode, $isFree, $nearestDate, $dateFormat) {
$intervalsData = [];
if (!empty($intervals)) {
foreach ($intervals[date('Ymd', strtotime($workDate))] as $key => $interval) {
if ($interval['schedident'] == $schedident) {
$intervalsData[$key]['time'] = $interval['time'];
$intervalsData[$key]['rNum'] = isset($interval['rNum'])? $interval['rNum']: null;
$intervalsData[$key]['startTime'] = explode('-', $interval['time'])[0];
$intervalsData[$key]['endTime'] = explode('-', $interval['time'])[1];
$intervalsData[$key]['schedident'] = $interval['schedident'];
$intervalsData[$key]['isFree'] = $interval['isFree'];
$intervalsData[$key]['onlineMode'] = $onlineMode;
$intervalsData[$key]['workDate'] = $interval['workDate']->format($dateFormat);
if ($interval['isFree'] && $isFree && is_null($nearestDate)) {
$nearestDate = $interval['workDate']->format('Y-m-d');
$intervalsData[$key]['nearestDate'] = $interval['workDate']->format($dateFormat);
$isFree = false;
}
}
}
}
return $intervalsData;
};
$dataResponse = [];
$i = 0;
if (isset($schedules['success'])) {
if ($schedules['success'] == true) {
$uniqueIntervals = [];
foreach ($schedules['data'] as $key => $data) {
uasort($data['intervals'], function ($a, $b) {
if ($a['workDate'] == $b['workDate']) {
return $a['startInterval'] <=> $b['startInterval'];
}
return 0;
});
foreach($data['intervals'] as $interval) {
if ($interval['isFree'] === true) {
$workDate = date($dateFormat, strtotime($interval['workDate']));
$uniqueKey = $interval['schedident']
. '-' . $workDate
. '-' . $interval['startInterval']
. '-' . $interval['endInterval'];
if (!isset($uniqueIntervals[$uniqueKey])) {
$dataIntervals = $findInterval($interval['schedident'], $workDate);
if ($dataIntervals) {
$uniqueIntervals[$uniqueKey] = [
'workDate' => $workDate,
'isFree' => $interval['isFree'],
'startInterval' => $interval['startInterval'],
'endInterval' => $interval['endInterval'],
'intervals' => $dataIntervals
];
}
}
}
}
}
$dataResponse = array_values($uniqueIntervals);
}
}
$uid = false;
if (! is_null($this->getUser())) {
$uid = $this->getUser()->getUid();
}
return $this->json(['data' => ['userInfo' => $uid, 'intervalsData' => $dataResponse]]);
}
private function getInterval($doctor, $department, $filial, $onlineMode, $startInterval, $endInterval)
{
$response = $this->client->request('GET', '/api/reservation/intervals', [
'verify_peer' => false,
'verify_host' => false,
'base_uri' => $_ENV['MIS'],
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'sovamed_bot'
],
'query' => [
'dcode' => $doctor,
'spec' => $department,
'onlineMode' => ($onlineMode ? 1 : 0),
'st' => \date("Ymd", strtotime($startInterval)),
'en' => \date("Ymd", strtotime($endInterval)),
'filialId' => $filial,
'inFilials' => $filial
]
]);
$intervals = $response->toArray();
$dataResponse = [];
if (isset($intervals['data'])) {
foreach ($intervals['data'] as $data) {
if (isset($data['workdates'])) {
foreach ($data['workdates'] as $key => $workdates) {
foreach ($workdates as $workdate => $item) {
$workDate = \DateTime::createFromFormat(
'Ymd',
$workdate
);
$intervalKey = 0;
for ($i=0; $i < count($item); $i++) {
foreach ($item[$i]['intervals'] as $intervaldata) {
$dataResponse[$workdate][$intervalKey]['workDate'] = $workDate;
$dataResponse[$workdate][$intervalKey]['schedident'] = $item[$i]['schedident'];
$dataResponse[$workdate][$intervalKey]['time'] = $intervaldata['time'];
$dataResponse[$workdate][$intervalKey]['isFree'] = $intervaldata['isFree'];
$dataResponse[$workdate][$intervalKey]['rNum'] = isset($item[$i]['rnum'])? $item[$i]['rnum']: null;
$intervalKey++;
}
}
}
}
}
}
}
return $dataResponse;
}
private function getSchedule($doctor, $department, $filial, $onlineMode, $startInterval, $endInterval)
{
$response = $this->client->request('GET', '/api/reservation/schedule', [
'verify_peer' => false,
'verify_host' => false,
'base_uri' => $_ENV['MIS'],
'headers' => [
'Content-Type' => 'application/json',
'User-Agent' => 'sovamed_bot'
],
'query' => [
'doctor' => $doctor,
'department' => $department,
'onlineMode' => ($onlineMode ? 1 : 0),
'st' => date("Ymd", strtotime($startInterval)),
'en' => date("Ymd", strtotime($endInterval)),
'filialId' => $filial
]
]);
return $response->toArray();
}
/**
* @Route("/userInfo", methods={"GET"})
*/
public function user(): Response
{
$uid = false;
if (! is_null($this->getUser())) {
$uid = $this->getUser()->getUid();
}
return $this->json(['data' => $uid]);
}
/**
* @OA\Get(
* tags= {"Услуги и цены"},
* path="/pricelist/departments",
* summary="Получение списка отделений",
* @OA\Response(
* response=200,
* description="json response"
* )
* )
*
* @Route("/pricelist/departments", methods={"GET"})
*/
public function pricelistDepartments(Request $request): Response
{
$response = [];
$entityManager = $this->getDoctrine()->getManager();
$departments = $entityManager->getRepository(PriceDepartment::class)
->findAll();
if ($departments) {
foreach ($departments as $key => $item) {
$item = $item->toArray();
unset($item['__initializer__']);
unset($item['__isInitialized__']);
unset($item['__cloner__']);
unset($item['id']);
$response[$key] = $item;
}
}
return $this->json(['data' => $response]);
}
/**
* @OA\Get(
* tags= {"Услуги и цены"},
* path="/pricelist",
* summary="Получение списка услуг и цен",
* @OA\Parameter(
* name="depnum",
* description="ID отделения",
* in="query",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="filial",
* description="ID филиала",
* in="query",
* required=false,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="active",
* description="Только активные",
* in="query",
* required=false,
* @OA\Schema(
* type="boolean"
* )
* ),
* @OA\Response(
* response=200,
* description="json response"
* )
* )
*
* @Route("/pricelist", methods={"GET"})
*/
public function pricelist(Request $request, PaginatorInterface $paginator, PriceListService $priceListService): JsonResponse
{
$params = [
'kodoper' => $request->query->get('kodoper'),
'groupId' => $request->query->get('depnum'),
'filial' => $request->query->get('filial'),
'actual' => $request->query->get('active')
];
$priceListQuery = $priceListService->getPriceListQuery($params);
$pagination = $paginator->paginate(
$priceListQuery->getQuery(),
$request->query->getInt('page', 1),
1000
);
$totalItems = $pagination->getTotalItemCount(); // Общее количество элементов
$itemCount = $pagination->count(); // Количество элементов на текущей странице
$currentPage = $pagination->getCurrentPageNumber(); // Текущая страница
$totalPages = ceil($totalItems / 1000); // Общее количество страниц
return $this->json([
'items' => $pagination,
'totalItems' => $totalItems,
'totalPages' => $totalPages,
'currentPage' => $currentPage,
'itemCount' => $itemCount
]);
}
private function getSpecialistResponse($specialist)
{
$response = [];
if ($specialist) {
$response = $specialist->toArray();
unset($response['pecialistMore']);
$response['img'] = 'https://api.sovamed.ru/specialist/picture/' . $specialist->getId();
if (!empty($response['kinder'])) {
$response['kinder'] = $response['kinder'] . ' ' . $this->textYear($response['kinder'], false);
}
if (!empty($response['experience'])) {
$response['experience'] = $response['experience'] . ' ' . $this->textYear($response['experience'], true);
}
$specialistMore = $specialist->getSpecialistMore();
if ($defaultLocation = $specialistMore->defaultLocation()) {
$response['nearestDate'] = $defaultLocation['nearestDate'];
$response['filial'] = [
'id' => $defaultLocation['filial'],
'address' => $defaultLocation['address'],
];
$response['department'] = [
'id' => $defaultLocation['department'],
'name' => $defaultLocation['name'],
];
}
$response['reviews'] = $specialistMore->getReviews();
$response['prices'] = $specialistMore->getPrices();
}
return $response;
}
private function textYear($year, $exp = true)
{
$t1 = 0;
$t2 = 0;
$year = abs($year);
$t1 = $year % 10;
$t2 = $year % 100;
if ($exp) {
return ($t1 == 1 && $t2 != 11 ? "год" : ($t1 >= 2 && $t1 <= 4 && ($t2 < 10 || $t2 >= 20) ? "года" : "лет"));
} else {
return ($t1 == 1 ? "года" : "лет");
}
}
/**
* @OA\Get(
* tags= {"Врачи"},
* path="/doctor",
* summary="Получение данных о враче",
* @OA\Parameter(
* name="sid",
* description="ID врача",
* in="query",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="reviews",
* description="Показывать отзывы",
* in="query",
* required=false,
* @OA\Schema(
* type="boolean",
* default=false
* )
* ),
* @OA\Response(
* response=200,
* description="json response"
* )
* )
*
* @Route("/doctor", methods={"GET"})
*/
public function doctor(SpecialistService $specialistService, Request $request): Response
{
if (empty($request->query->getInt('sid'))) {
return $this->json(['data' => false]);
}
$specialist = $specialistService->show([
'dcode' => $request->query->get('sid')
]);
return $this->json(['data' => $this->getSpecialistResponse($specialist)]);
}
/**
* @OA\Get(
* tags= {"Врачи"},
* path="/doctors/{region}",
* summary="Получение данных врачей по регионам",
* @OA\Parameter(
* name="region",
* description="Название города",
* in="path",
* required=true,
* @OA\Schema(
* type="string",
* default="saratov"
* )
* ),
* @OA\Parameter(
* name="reviews",
* description="Показывать отзывы",
* in="query",
* required=false,
* @OA\Schema(
* type="boolean",
* default=true
* )
* ),
* @OA\Response(
* response=200,
* description="json response"
* )
* )
*
* @Route("/doctors/{region}", methods={"GET"})
*/
public function index(SpecialistService $specialistService, Request $request, $region = 'saratov'): Response
{
$regionId = match($region) {
'krasnodar' => 94,
'voronej' => 93,
'volgograd' => 92,
default => 91
};
$pagination = $specialistService->listPaginated(
['regionId' => $regionId],
$request->query->getInt('page', 1),
500
);
$totalItems = $pagination->getTotalItemCount(); // Общее количество элементов
$itemCount = $pagination->count(); // Количество элементов на текущей странице
$currentPage = $pagination->getCurrentPageNumber(); // Текущая страница
$totalPages = ceil($totalItems / 1000); // Общее количество страниц
$response = [];
foreach ($pagination as $key => $specialist) {
$response[$key] = $this->getSpecialistResponse($specialist);
}
return $this->json([
'data' => $response,
'totalItems' => $totalItems,
'totalPages' => $totalPages,
'currentPage' => $currentPage,
'itemCount' => $itemCount
]);
}
}