<?php
namespace App\Controller;
use App\Entity\Candidate;
use App\Enum\CalendarEvent\Type;
use App\Enum\Payment\Type as PaymentType;
use App\Form\TipSettingsType;
use App\Service\CalendarEvent\CalendarEventService;
use App\Service\Image\ImageService;
use App\Service\ReviewerSettingsService;
use App\Service\TelegramCampaignParticipant\TelegramCampaignParticipantService;
use App\Service\TipSettingsService;
use App\Service\User\UserService;
use App\Service\UserSettingsService;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\User;
use App\Enum\Candidate\Status;
use App\Form\CandidateType;
use App\Service\PaymentService;
use App\Library\PyTg\PyTgUtils;
use App\Library\Utils\ApiHandler;
use App\Service\Candidate\CandidateService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Service\RouteService;
use App\Service\ServiceRetriever;
use App\Service\FileService;
use App\Service\TelegramCampaign\TelegramCampaignService;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
class CandidateController extends BaseAbstractController
{
/*
* @var PyTgUtils
*/
private $pyTgUtils;
/**
* @var TelegramCampaignService
*/
private $telegramCampaignService;
public function __construct(Security $security, RequestStack $requestStack,
ValidatorInterface $validator, RouteService $routeService,
RouterInterface $router, SessionInterface $session, EntityManagerInterface $em,
FileService $fileService,
ServiceRetriever $serviceRetriever, PyTgUtils $pyTgUtils,
TelegramCampaignService $telegramCampaignService)
{
parent::__construct($security, $requestStack, $validator, $routeService, $router, $session,
$em, $fileService, $serviceRetriever);
$this->pyTgUtils = $pyTgUtils;
$this->telegramCampaignService = $telegramCampaignService;
}
public function review(Request $request, PaymentService $paymentService, CandidateService $candidateService,
PyTgUtils $pyTgUtils, ReviewerSettingsService $reviewerSettingsService, UserService $userService,
TipSettingsService $tipSettingsService, UserSettingsService $settingsService): Response
{
$candidates = $candidateService->getForReview();
return $this->render('admin/candidate/candidates_review.html.twig', [
'candidates' => $candidates,
]);
}
public function approve(Request $request, CandidateService $candidateService): JsonResponse
{
return ApiHandler::handleApiRequest($request, function (&$responseCode, $content) use ($candidateService) {
$candidateId = $content["candidate_id"];
$recommendations = $content["recommendations"];
/**
* @var Candidate
*/
$candidate = $candidateService->getBaseService()->get($candidateId);
if (!$candidate) {
$responseCode = 400;
return null;
}
$candidateService->setStatusApproved($candidate, $recommendations);
// try {
// $this->notifyCandidateReviewResult($candidate);
// } catch (\Throwable $e) {
// //ignore notification errors
// }
$responseCode = 200;
return null;
}, true, ["candidate_id", "recommendations"]);
}
private function setPyTgData(User $user)
{
$this->pyTgUtils->pyTg->setPhone($user->getTelegramPhone());
$this->pyTgUtils->pyTg->setAccountName($user->getTelegramPhone());
$this->pyTgUtils->pyTg->setPythonTelegramPath($_ENV['PYTHON_TELEGRAM_PATH']);
}
private function notifyCandidateReviewResult(Candidate $candidate)
{
// $this->setPyTgData($candidate->getAuthor());
// $this->telegramCampaignService->addCandidateToCampaign($candidate);
// $result = $this->pyTgUtils->pyTg->sendMessage($candidate->getPerson()->getName(), $message);
}
public function decline(Request $request, CandidateService $candidateService): JsonResponse
{
return ApiHandler::handleApiRequest($request, function (&$responseCode, $content) use ($candidateService) {
$candidateId = $content["candidate_id"];
$recommendations = $content["recommendations"];
$declineComment = $content["decline_comment"];
/** @var Candidate $candidate */
$candidate = $candidateService->getBaseService()->get($candidateId);
if (!$candidate) {
$responseCode = 400;
return null;
}
$candidateService->setStatusDeclined($candidate, $recommendations, $declineComment);
// try {
// $this->notifyCandidateReviewResult($candidate);
// } catch (\Throwable $e) {
// //ignore notification errors
// }
$responseCode = 200;
return null;
}, true, ["candidate_id", "recommendations", "decline_comment"]);
}
public function list(Request $request, CandidateService $candidateService,
CalendarEventService $calendarEventService): Response
{
$items = $candidateService->getList(["createdAt", "DESC"], null,
null);
$calendarEvents = $calendarEventService->getAll(null,
Type::getListTypes());
// return $this->redirectToRoute("login");
return $this->render('admin/candidate/candidates.html.twig', [
'items' => $items,
"calendarEvents" => $calendarEvents,
]);
}
public function innerPayments(Request $request, CandidateService $candidateService,
CalendarEventService $calendarEventService, PaymentService $paymentService): Response
{
$items = $candidateService->getList(["createdAt", "DESC"],
null, null);
// Оставляем только кандидатов, у которых есть цена мероприятия
$items = array_filter($items, function (Candidate $candidate) use ($paymentService) {
if ($candidate->getStatus() !== Status::STATUS_APPROVED) {
return false;
}
$ce = $candidate->getCalendarEvent();
if (!$ce || !$ce->getPrice()) {
return false;
}
$person = $candidate->getPerson();
if (!$person) {
return false;
}
// Если у человека есть платежи по этому мероприятию с type != inner — исключаем кандидата
$payments = $paymentService->getDefault([
'person' => $person,
'calendarEvent' => $ce,
]);
foreach ($payments as $p) {
if ($p->getType() !== PaymentType::TYPE_INNER) {
return false;
}
}
return true;
});
$calendarEvents = $calendarEventService->getAll(null, Type::getListTypes());
$hasInnerMap = [];
foreach ($items as $it) {
$person = $it->getPerson();
$ce = $it->getCalendarEvent();
$hasInner = false;
if ($person && $ce) {
// Используем метод сервиса для проверки наличия полной оплаты у человека по мероприятию
$hasInner = $paymentService->hasPersonFullPay($person, $ce);
}
$hasInnerMap[$it->getId()] = $hasInner;
}
return $this->render('admin/candidate/inner_payments.html.twig', [
'items' => $items,
'calendarEvents' => $calendarEvents,
'hasInnerMap' => $hasInnerMap,
]);
}
public function toggleInnerPaymentAction(Request $request, CandidateService $candidateService,
PaymentService $paymentService): JsonResponse
{
return ApiHandler::handleApiRequest($request, function (&$responseCode, $content) use ($candidateService, $paymentService) {
$candidateId = $content['candidate_id'] ?? null;
$actionType = $content['actionType'] ?? null;
if (!$candidateId) {
$responseCode = 400;
return ["message" => "Не указан идентификатор кандидата."];
}
if (!in_array($actionType, ['delete_payment', 'add_payment'], true)) {
$responseCode = 400;
return ["message" => "Не указан или некорректен параметр actionType. Ожидается 'delete_payment' или 'add_payment'."];
}
/** @var Candidate|null $candidate */
$candidate = $candidateService->getBaseService()->get($candidateId);
if (!$candidate) {
$responseCode = 404;
return ["message" => "Кандидат не найден."];
}
// Проверка статуса
if ($candidate->getStatus() !== Status::STATUS_APPROVED) {
$responseCode = 400;
return ["message" => "Кандидат не в статусе Принят. Операция невозможна."];
}
// Поиск существующей внутренней оплаты (только type = inner)
$existingPayments = $paymentService->getDefault([
'person' => $candidate->getPerson(),
'calendarEvent' => $candidate->getCalendarEvent(),
'type' => PaymentType::TYPE_INNER,
]);
if ($actionType === 'delete_payment') {
if ($existingPayments) {
try {
foreach ($existingPayments as $p) {
$this->em->remove($p);
}
$this->em->flush();
$responseCode = 200;
return ["success" => true, "action" => "deleted", "message" => "Внутренние оплаты удалены"];
} catch (\Throwable $e) {
$responseCode = 500;
return ["message" => "Ошибка при удалении платежей: " . $e->getMessage()];
}
} else {
$responseCode = 200;
return ["success" => true, "action" => "deleted", "message" => "Внутренние оплаты отсутствуют"];
}
}
// actionType === 'add_payment'
try {
// Если у человека уже есть полная оплата по этому мероприятию — не создаём внутреннюю оплату
if ($paymentService->hasPersonFullPay($candidate->getPerson(), $candidate->getCalendarEvent())) {
$responseCode = 400;
return ["message" => "У человека уже есть полная оплата"];
}
$paymentClass = '\\App\\Entity\\Payment';
/** @var \App\Entity\Payment $payment */
$payment = new $paymentClass();
$payment->setAmount($candidate->getCalendarEvent()->getPrice());
$payment->setComment('Внутренняя оплата');
$payment->setCalendarEvent($candidate->getCalendarEvent());
$payment->setPerson($candidate->getPerson());
$payment->setCreatedBy($this->getLoggedInUser());
$payment->setDateTime(new \DateTime());
$payment->setType(PaymentType::TYPE_INNER);
$this->em->persist($payment);
$this->em->flush();
$responseCode = 200;
return ["success" => true, "action" => "created", "message" => "Внутренняя оплата создана"];
} catch (\Throwable $e) {
$responseCode = 500;
return ["message" => "Ошибка при создании платежа: " . $e->getMessage()];
}
}, true, ['candidate_id', 'actionType']);
}
public function edit(Request $request, CandidateService $candidateService, EntityManagerInterface $em,
TelegramCampaignParticipantService $telegramCampaignParticipantService): Response
{
$id = $request->get('id');
/**
* @var Candidate|null $item
*/
$item = $id ? $candidateService->getBaseService()->get($id) : null;
if (!$item) {
$item = (new Candidate());
}
$oldPerson = $item->getPerson();
$form = $this->createForm(CandidateType::class, $item);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$hasErrors = false;
if ($item->getStatus() === Status::STATUS_DRAFT) {
$this->addFlash('errors', 'Нельзя установить статус черновика. Выберите другой статус.');
$hasErrors = true;
}
if (!$hasErrors) {
if ($oldPerson->getId() != $item->getPerson()->getId()) {
$this->addFlash('success', 'Имя кандидата "'
. $oldPerson->getName() . '" изменено на "' . $item->getPerson()->getName() . '".');
}
$removedFromCampaigns = $telegramCampaignParticipantService
->removeAllReviewCampaignsParticipantsWithCandidate($item, $oldPerson);
if ($this->isUserAdmin()) {
foreach ($removedFromCampaigns as $campaign) {
$this->addFlash('success', 'Кандидат исключён из рассылки "' . $campaign->getTitle() . "\"");
}
if ($removedFromCampaigns) {
$this->addFlash('success', 'Кандидат будет добавлен в необходиые рассылки при необходимости повторно.');
}
}
$item->setUpdatedAt(new \DateTime());
$em->persist($item);
$em->flush();
return $this->redirectToRoute('moderator_candidates');
}
}
return $this->render('admin/candidate/candidate_edit.html.twig', [
'form' => $form->createView(),
'item' => $item,
]);
}
public function delete(Request $request, CandidateService $candidateService,
TelegramCampaignParticipantService $campaignParticipantService, ImageService $imageService): RedirectResponse
{
$id = $request->get('id');
if (!$id) {
$this->addFlash('errors', 'Не указан идентификатор кандидата.');
return $this->redirectToRoute('moderator_candidates');
}
/** @var Candidate|null $candidate */
$candidate = $candidateService->getBaseService()->get($id);
if (!$candidate) {
$this->addFlash('errors', 'Кандидат не найден.');
return $this->redirectToRoute('moderator_candidates');
}
try {
$campaignParticipantService->removeAllParticipantsWithCandidate($candidate);
} catch (\Throwable $e) {
$this->addFlash('errors', 'Ошибка при удалении связанных участников рассылок. ' . $e->getMessage() . '.');
return $this->redirectToRoute('moderator_candidates');
}
// $candidate->setImage(null);
// $this->em->flush();
try {
// $this->em->remove($candidate);
$candidate->setStatus(Status::STATUS_DELETED);
$this->em->flush();
$this->addFlash('success', 'Кандидат ' . $candidate->getPerson()->getName() . ' удалён.');
} catch (\Throwable $e) {
$this->addFlash('errors', 'Ошибка при удалении кандидата. ' . $e->getMessage() . '.');
}
return $this->redirectToRoute('moderator_candidates');
}
}