<?php
namespace App\Service\Candidate;
use App\Entity\CalendarEvent;
use App\Entity\Candidate;
use App\Entity\Image;
use App\Entity\Invoice;
use App\Entity\Person;
use App\Entity\Student;
use App\Entity\User;
use App\Enum\CalendarEvent\Type;
use App\Enum\Candidate\Status;
use App\Library\Utils\Other\Other;
use App\Service\BaseEntityService;
use App\Service\CalendarEvent\CalendarEventService;
use App\Service\PaymentService;
use App\Service\Person\PersonService;
use Doctrine\ORM\EntityManagerInterface;
class CandidateService extends BaseEntityService
{
/**
* @var PaymentService
*/
private $paymentService;
/**
* @var CalendarEventService
*/
private $calendarEventService;
public function __construct(EntityManagerInterface $em, PaymentService $paymentService,
CalendarEventService $calendarEventService)
{
parent::__construct($em);
$this->initialize(Candidate::class);
$this->paymentService = $paymentService;
$this->calendarEventService = $calendarEventService;
}
/**
* @return Candidate[]
*/
public function getForReview(): array
{
return $this->getDefault([
"status" => Status::STATUS_NEW,
"calendarEvent.isCandidateReviewRequired" => true,
], null, null, [CalendarEvent::class]);
}
/**
* @return Candidate[]
*/
public function getList(array $sortBy = null, CalendarEvent $calendarEvent = null,
?bool $isCandidateReviewRequired = true, \DateTime $start = null, \DateTime $end = null): array
{
$params = [
"status" => Status::getListStatuses(),
];
if ($calendarEvent) {
$params["calendarEvent"] = $calendarEvent;
}
if ($isCandidateReviewRequired !== null) {
$params["calendarEvent.isCandidateReviewRequired"] = $isCandidateReviewRequired;
}
/** @var Candidate[] $items */
$items = $this->getDefault($params, null,
null, [CalendarEvent::class], null, $sortBy);
if ($start || $end) {
$items = $this->filterCandidatesByCalendarEventEndDate($items, $start, $end);
}
return $items;
}
/**
* @param array|Candidate[] $items
* @return array|Candidate[]
*/
public function filterCandidatesByCalendarEventEndDate(array $items,
\DateTime $start = null, \DateTime $end = null): array
{
if ($start || $end) {
$items = array_filter($items, function ($item) use ($start, $end) {
$ce = $item->getCalendarEvent();
if ($ce) {
$ceEnd = $ce->getEndDate();
if ($start && $ceEnd < $start) {
return false;
}
if ($end && $ceEnd > $end) {
return false;
}
return true;
}
return false;
});
}
return $items;
}
/**
* @return array<Candidate>
*/
public function getApprovedCandidates($sortByName = false, CalendarEvent $calendarEvent = null): array
{
$qb = $this->em->getRepository(Candidate::class)->createQueryBuilder('c');
$qb->where('c.status = :status')
->setParameter('status', Status::STATUS_APPROVED)
->join('c.person', 'p');
if ($calendarEvent) {
$qb->andWhere('c.calendarEvent = :calendarEvent')
->setParameter('calendarEvent', $calendarEvent);
}
if ($sortByName) {
$qb
->orderBy('p.lastName', 'ASC');
}
return $qb->getQuery()->getResult();
}
/**
* @param array $statuses
* @return Candidate[]
*/
public function getByStatuses(array $statuses): array
{
return $this->getDefault([
"status" => $statuses,
]);
}
/**
* @return Candidate[]
*/
public function getApprovedAndDeclined(): array
{
return $this->getDefault([
"status" => [Status::STATUS_APPROVED, Status::STATUS_DECLINED],
]);
}
/**
* @param Candidate[] $candidates
* @param CalendarEvent $calendarEvent
* @return array{summaryAmount: float, candidate: Candidate, isPayed: bool, debtAmount: float, lastPayment: ?\App\Entity\Payment}[]
*/
public function getCandidatesPaymentsData($candidates, $calendarEvent)
{
$candidatePaymentsData = [];
$candidatePaymentsDataNotPayed = [];
$candidatePaymentsDataPartial = [];
$candidatePaymentsDataFull = [];
$candidatePaymentsDataByReceipt = [];
/**
* @var Candidate $candidate
*/
foreach ($candidates as $candidate) {
$payments = $this->paymentService->getDefault([
"person" => $candidate->getPerson(),
"calendarEvent" => $calendarEvent,
]);
$amount = array_reduce($payments, function($carry, $item) {
return $carry + $item->getAmount();
}, 0);
$candidatePaymentData = [
'summaryAmount' => $amount,
'candidate' => $candidate,
'isPayed' => $amount >= $candidate->getCalendarEvent()->getPrice(),
'hasAnyPayments' => $amount,
'debtAmount' => max(0, $candidate->getCalendarEvent()->getPrice() - $amount),
'lastPayment' => count($payments) ? $payments[count($payments) - 1] : null,
];
$payStatus = $amount >= $candidate->getCalendarEvent()->getPrice() ? "full"
: (($candidate->isAccessByReceipt() ? "by_receipt" : ($amount == 0 ? "not_payed" : "partial")));
switch ($payStatus) {
case "full":
$candidatePaymentsDataFull[] = $candidatePaymentData;
break;
case "not_payed":
$candidatePaymentsDataNotPayed[] = $candidatePaymentData;
break;
case "partial":
$candidatePaymentsDataPartial[] = $candidatePaymentData;
break;
case "by_receipt":
$candidatePaymentsDataByReceipt[] = $candidatePaymentData;
break;
default:
throw new \Exception("Unknown: ", $payStatus);
}
}
$candidatePaymentsData = array_merge($candidatePaymentsDataFull,
$candidatePaymentsDataPartial, $candidatePaymentsDataNotPayed, $candidatePaymentsDataByReceipt);
return $candidatePaymentsData;
}
/**
* @return Candidate[]
*/
public function getZoomListCandidates(CalendarEvent $calendarEvent): array
{
$candidates = $this->getDefault([
"calendarEvent" => $calendarEvent,
"status" => Status::STATUS_APPROVED,
]);
$candidatesPaymentsData = $this->getCandidatesPaymentsData($candidates, $calendarEvent);
$candidatesPaymentsData = array_filter($candidatesPaymentsData, function ($data) {
return $data['isPayed'];
});
$candidates = array_map(function ($data) {
return $data['candidate'];
}, $candidatesPaymentsData);
return $candidates;
}
public function createOrGetApproved(Person $person, CalendarEvent $event, User $author,
\DateTime $createdAt = null): Candidate
{
$candidate = $this->getFirst([
"person" => $person,
"calendarEvent" => $event,
]);
if (!$candidate) {
$candidate = (new Candidate())
->setPerson($person)
->setCalendarEvent($event)
->setStatus(Status::STATUS_APPROVED)
->setAuthor($author)
;
if ($createdAt) {
$candidate->setCreatedAt($createdAt);
}
$this->em->persist($candidate);
$this->em->flush();
}
return $candidate;
}
public function createDraft(User $author): Candidate
{
$candidate = (new Candidate())
->setAuthor($author)
->setStatus(Status::STATUS_DRAFT)
;
$this->em->persist($candidate);
$this->em->flush();
return $candidate;
}
public function getLastDraft(User $author): ?Candidate
{
return $this->getBaseService()->getLast([
"author" => $author,
"status" => Status::STATUS_DRAFT,
]);
}
public function getLastNonDraftCandidate(User $author): ?Candidate
{
return $this->getBaseService()->getLast([
"author" => $author,
"status" => Status::getForCloneCandidateStatuses()
]);
}
public function copyCandidateDataToDraft(Candidate $fromCandidate, Candidate $draft): void
{
$draft->setPerson($fromCandidate->getPerson());
$draft->setComment($fromCandidate->getComment());
$draft->setAuthor($fromCandidate->getAuthor());
$draft->setCalendarEvent($fromCandidate->getCalendarEvent());
$draft->setReviewApproveComment($fromCandidate->getReviewApproveComment());
$draft->setCurator($fromCandidate->getCurator());
$draft->setStatus(Status::STATUS_DRAFT);
$this->em->flush();
}
public function existsSame(Candidate $candidate): bool
{
if ($candidate->getPerson() && $candidate->getCalendarEvent()) {
$sameCandidates = $this->getDefault([
"person" => $candidate->getPerson(),
"calendarEvent" => $candidate->getCalendarEvent(),
"status" => [Status::STATUS_NEW, Status::STATUS_APPROVED],
]);
return count($sameCandidates) > 0;
}
return false;
}
/**
* @param Candidate $candidate
* @param $status
* @param bool $checkPhoto
* @return array{result: bool, needFields: array, candidate: Candidate}
*/
public function confirmCandidate(Candidate $candidate, $status = Status::STATUS_NEW, bool $checkPhoto = true): array
{
$result = [
"result" => false,
"needFields" => [],
"candidate" => $candidate,
];
$ce = $candidate->getCalendarEvent();
$person = $candidate->getPerson();
$student = $person ? $person->getStudent() : null;
$isCeFinished = $ce
&& $ce->isFinished();
$isCeCandidateReviewRequired = $ce
&& $ce->isIsCandidateReviewRequired();
$isCalendarEventsParticipationsRequired = $ce
&& $ce->getRequireCalendarEventsParticipations()->count();
$isDeniedCalendarEventParticipationCheckRequired = $ce
&& $ce->getDenyCalendarEventsParticipations()->count();
$isRegistrationOpen = $ce
&& $ce->isRegistrationOpen();
$isStatusApproved = $status == Status::STATUS_APPROVED;
$isCalendarEventTypeRequiresApproveReviewComment = $ce
&& Type::isApproveReviewCommentRequireType($ce->getType());
/** @var CalendarEvent[] $missingRequiredCandidateCalendarEvents */
$missingRequiredCandidateCalendarEvents = null;
/** @var CalendarEvent[] $deniedCalendarEventsParticipations */
$deniedCalendarEventsParticipations = null;
$requiredKeys = ['person', 'calendarEvent', "comment", "curator", "levelCompliance",
"studentStatusCompliance"];
if ($person && (!$person->getStudent()
|| $person->getStudent()->getLevel() <= 1)) { //todo change this hardcode. Добавить 1-й так же без комментария
$requiredKeys = array_filter($requiredKeys, function ($key) {
return $key != "comment";
});
}
if ((!$isRegistrationOpen || ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))
&& !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'reviewApproveComment';
}
if (!$isRegistrationOpen && !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'statusApprovedForClosedCalendarEvent';
}
if ($checkPhoto && $ce && $ce->isIsCandidateReviewRequired()
&& (!$student || !$student->isCandidateReviewIsNotRequired())) {
$requiredKeys[] = 'image';
}
if ($isCalendarEventsParticipationsRequired) {
$requiredKeys[] = 'calendarEventsParticipations';
if ($ce && $person) {
$missingRequiredCandidateCalendarEvents =
$this->calendarEventService->getMissingRequiredCandidateCalendarEvents(
$person, $ce
);
}
}
if ($isDeniedCalendarEventParticipationCheckRequired) {
$requiredKeys[] = 'deniedCalendarEventsParticipations';
if ($ce && $person) {
$deniedCalendarEventsParticipations =
$this->calendarEventService->getDeniedCalendarEventsForPerson(
$person, $ce
);
}
}
$isFieldNotRequired = [
"person" => !!$person,
"calendarEvent" => $ce ? true : false,
"image" => !$checkPhoto || $status == Status::STATUS_APPROVED
|| ((($isCeFinished || $isCeCandidateReviewRequired)) && $candidate->getImage())
|| ($student && $student->isCandidateReviewIsNotRequired()) ? true : false,
"reviewApproveComment" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS || !(!$candidate->getReviewApproveComment()
&& ($ce
&& (!$ce->isRegistrationOpen()
|| ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))))
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"statusApprovedForClosedCalendarEvent" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS
|| !$ce || ($student && $student->isCandidateReviewIsNotRequired())
|| $ce->isRegistrationOpen()
|| $status == Status::STATUS_APPROVED,
"comment" => !!$candidate->getComment() ||
($student && $student->isCandidateReviewIsNotRequired())
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"curator" => !!$candidate->getCurator(),
"calendarEventsParticipations" => !$isCalendarEventsParticipationsRequired
|| ($missingRequiredCandidateCalendarEvents !== null
&& count($missingRequiredCandidateCalendarEvents) === 0),
"deniedCalendarEventsParticipations" => !$isDeniedCalendarEventParticipationCheckRequired
|| ($deniedCalendarEventsParticipations !== null
&& count($deniedCalendarEventsParticipations) === 0),
"levelCompliance" => !$ce || $ce->getFromLevel() === null || !$person
|| ($student && $student->getLevel() >= $ce->getFromLevel()
&& ($ce->getToLevel() === null || $student->getLevel() <= $ce->getToLevel())),
"studentStatusCompliance" => !$ce || $ce->getRequireStudentStatus() === null || !$person
|| ($person->getVirtualStudentStatus() == $ce->getRequireStudentStatus()),
];
$fieldTexts = [
"person" => "ФИО",
"calendarEvent" => "мероприятие",
"image" => "фото",
"reviewApproveComment" => "комментарий согласования о пройденном отборе",
"statusApprovedForClosedCalendarEvent" =>
"статус должен быть \"" . Status::getText(Status::STATUS_APPROVED)
. "\" для закрытой регистрации на мероприятие",
"comment" => "комментарий",
"curator" => "куратор",
"calendarEventsParticipations" => "отсуствует участие в обязательных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $missingRequiredCandidateCalendarEvents ?? [])),
"deniedCalendarEventsParticipations" => "есть участие в исключенных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $deniedCalendarEventsParticipations ?? [])),
"levelCompliance" => ($ce ? "несоответствие уровня участника: "
. 'необходим ' . $ce->getFromLevel() . " ур." : ""),
"studentStatusCompliance" => ($ce ? "несоответствие статуса участника: "
. 'необходим ' . $ce->getRequireStudentStatusText() . " ур." : ""),
];
foreach ($isFieldNotRequired as $fieldName => $isSet) {
if (!$isSet) {
$result['needFields'][] = [
"field" => $fieldName,
"text" => $fieldTexts[$fieldName],
];
}
}
$allRequiredFieldsSet = true;
foreach ($requiredKeys as $key) {
if (!$isFieldNotRequired[$key]) {
$allRequiredFieldsSet = false;
break;
}
}
if ($allRequiredFieldsSet
// && $candidate->getCurator()
) {
if ($ce &&
(!$ce->isIsCandidateReviewRequired() || $ce->isFinished()
|| ($student && $student->isCandidateReviewIsNotRequired()))) {
$this->setStatusApproved($candidate);
} else {
$this->setStatus($candidate, $status);
}
$result['result'] = true;
}
return $result;
}
public function setStatusNew(Candidate $candidate)
{
$candidate->setStatus(Status::STATUS_NEW);
$candidate->setCreatedAt(new \DateTime());
$candidate->setUpdatedAt(new \DateTime());
$this->em->flush();
}
/**
* @param Person $person
* @param CalendarEvent|CalendarEvent[] $calendarEvent
* @return Candidate[]
*/
public function getApprovedByPersonAndCalendarEvent(Person $person, $calendarEvent): array
{
return $this->getDefault([
"person" => $person,
"calendarEvent" => $calendarEvent,
"status" => [Status::STATUS_APPROVED],
]);
}
public function getCandidatesForStudentLevelUpdate(): array
{
return [];
}
/**
* @param array|Candidate[] $candidates
* @return array
*/
public function getCandidatesForPayments(array $candidates = null,
\DateTime $start = null, \DateTime $end = null): array
{
if (!$candidates) {
$candidates = $this->getApprovedCandidates(true);
if ($start || $end) {
$candidates = $this->filterCandidatesByCalendarEventEndDate($candidates, $start, $end);
}
}
$candidates2 = [];
foreach ($candidates as $candidate) {
$candidates2[] = [
"candidate" => $candidate,
"text" => PersonService::getFirstLastNameWithOld($candidate->getPerson())
. ($candidate->getPerson()->getLastName2() ? " [1]" : ""),
"isLastName2" => false,
];
if ($candidate->getPerson()->getLastName2()) {
$candidates2[] = [
"candidate" => $candidate,
"text" => PersonService::getFirstLastNameWithOld($candidate->getPerson(), true) . " [2]",
"isLastName2" => true,
];
}
}
usort($candidates2, function ($a, $b) {
return strcoll($a['text'], $b['text']);
});
return $candidates2;
}
public function toArray(array $candidates): array
{
$objToArrHandler = function ($currentObject, $currentObjectData) use (&$objToArrHandler) {
if (is_object($currentObject)) {
switch (str_replace("Proxies\__CG__\\", "", get_class($currentObject))) {
case Candidate::class:
$currentObjectData['person'] = Other::objectToArray($currentObject->getPerson(),
["id", "name", "phone", "email", "city"], $objToArrHandler);
$currentObjectData['calendarEvent'] = Other::objectToArray($currentObject->getCalendarEvent(),
["id", "name", "price"], $objToArrHandler);
break;
case Person::class:
$currentObjectData['city'] = Other::objectToArray($currentObject->getCity(),
["id", "name"]);
break;
case CalendarEvent::class:
// no additional fields
break;
default:
throw new \Exception("Unknown: " . get_class($currentObject));
}
}
return $currentObjectData;
};
$candidatesData = Other::objectToArray($candidates, ["id", "status", "person", "calendarEvent"],
$objToArrHandler);
return $candidatesData;
}
public function setStatusApproved(Candidate $candidate, string $recommendations = null)
{
$candidate->setStatus(Status::STATUS_APPROVED);
$candidate->setRecommendations($recommendations);
$candidate->setDeclineComment(null);
$candidate->setUpdatedAt(new \DateTime());
$this->em->flush();
}
public function setStatus(Candidate $candidate, string $status = null)
{
$candidate->setStatus($status);
$candidate->setUpdatedAt(new \DateTime());
$this->em->flush();
}
public function setStatusDeclined(Candidate $candidate, $recommendations, $declineComment)
{
$candidate->setStatus(Status::STATUS_DECLINED);
$candidate->setRecommendations($recommendations);
$candidate->setDeclineComment($declineComment);
$candidate->setUpdatedAt(new \DateTime());
$this->em->flush();
}
/**
* @return Candidate[]
*/
public function getAll(): array
{
return $this->getBaseService()->getAll();
}
/**
* Вернуть количество кандидатов сгруппированное по студентам и по мероприятиям.
* Возвращаемая структура: [
* studentId => [ calendarEventId|string('null') => count, ... ],
* ...
* ]
*
* @return array<int, array<string,int>>
*/
public function getCandidatesGroupedByStudentsAndCalendarEvents(): array
{
$qb = $this->em->createQueryBuilder();
// left join candidates through student->person relationship (c.person = p)
// выбираем минимальный candidate id (если несколько) чтобы иметь единичный candidate_id на пару студент-событие
$qb->select('s.id AS student_id, ce.id AS calendar_event_id, MIN(c.id) AS candidate_id')
->from(Student::class, 's')
->leftJoin('s.person', 'p')
// join Candidate entity explicitly with ON (WITH) c.person = p to keep students without candidates
->leftJoin('\App\\Entity\\Candidate', 'c', 'WITH', 'c.person = p')
->leftJoin('c.calendarEvent', 'ce')
// ->where($qb->expr()->in('s.status', ':statuses'))
// ->setParameter('statuses', \App\Enum\Student\Status::getWorkingStatuses())
->groupBy('s.id, ce.id');
// Используем getScalarResult для предсказуемых ключей в результирующих строках
$results = $qb->getQuery()->getScalarResult();
$groupedData = [];
foreach ($results as $row) {
$studentId = (int)$row['student_id'];
$calendarEventId = $row['calendar_event_id'] !== null ? (int)$row['calendar_event_id'] : null;
$candidateId = $row['candidate_id'] !== null ? (int)$row['candidate_id'] : null;
if (!isset($groupedData[$studentId])) {
$groupedData[$studentId] = [];
}
if ($calendarEventId === null) {
continue;
}
$calendarKey = $calendarEventId === null ? 'null' : (string)$calendarEventId;
$groupedData[$studentId][$calendarKey] = $candidateId;
}
return $groupedData;
}
/**
* @param Candidate $candidate
* @param $status
* @param bool $checkPhoto
* @return array{result: bool, needFields: array, candidate: Candidate}
*/
public function isPersonCorrespondToCalendarEvent(Person $person,
CalendarEvent $calendarEvent,
Image $image = null,
$status = Status::STATUS_NEW, bool $checkPhoto = true): array
{
$result = [
"result" => false,
"needFields" => [],
"candidate" => $candidate,
];
$ce = $candidate->getCalendarEvent();
$person = $candidate->getPerson();
$student = $person ? $person->getStudent() : null;
$isCeFinished = $ce
&& $ce->isFinished();
$isCeCandidateReviewRequired = $ce
&& $ce->isIsCandidateReviewRequired();
$isCalendarEventsParticipationsRequired = $ce
&& $ce->getRequireCalendarEventsParticipations()->count();
$isDeniedCalendarEventParticipationCheckRequired = $ce
&& $ce->getDenyCalendarEventsParticipations()->count();
$isRegistrationOpen = $ce
&& $ce->isRegistrationOpen();
$isStatusApproved = $status == Status::STATUS_APPROVED;
$isCalendarEventTypeRequiresApproveReviewComment = $ce
&& Type::isApproveReviewCommentRequireType($ce->getType());
/** @var CalendarEvent[] $missingRequiredCandidateCalendarEvents */
$missingRequiredCandidateCalendarEvents = null;
/** @var CalendarEvent[] $deniedCalendarEventsParticipations */
$deniedCalendarEventsParticipations = null;
$requiredKeys = ['person', 'calendarEvent', "comment", "curator", "levelCompliance",
"studentStatusCompliance"];
if ($person && (!$person->getStudent()
|| $person->getStudent()->getLevel() <= 1)) { //todo change this hardcode. Добавить 1-й так же без комментария
$requiredKeys = array_filter($requiredKeys, function ($key) {
return $key != "comment";
});
}
if ((!$isRegistrationOpen || ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))
&& !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'reviewApproveComment';
}
if (!$isRegistrationOpen && !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'statusApprovedForClosedCalendarEvent';
}
if ($checkPhoto && $ce && $ce->isIsCandidateReviewRequired()
&& (!$student || !$student->isCandidateReviewIsNotRequired())) {
$requiredKeys[] = 'image';
}
if ($isCalendarEventsParticipationsRequired) {
$requiredKeys[] = 'calendarEventsParticipations';
if ($ce && $person) {
$missingRequiredCandidateCalendarEvents =
$this->calendarEventService->getMissingRequiredCandidateCalendarEvents(
$person, $ce
);
}
}
if ($isDeniedCalendarEventParticipationCheckRequired) {
$requiredKeys[] = 'deniedCalendarEventsParticipations';
if ($ce && $person) {
$deniedCalendarEventsParticipations =
$this->calendarEventService->getDeniedCalendarEventsForPerson(
$person, $ce
);
}
}
$isFieldNotRequired = [
"person" => !!$person,
"calendarEvent" => $ce ? true : false,
"image" => !$checkPhoto || $status == Status::STATUS_APPROVED
|| ((($isCeFinished || $isCeCandidateReviewRequired)) && $candidate->getImage())
|| ($student && $student->isCandidateReviewIsNotRequired()) ? true : false,
"reviewApproveComment" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS || !(!$candidate->getReviewApproveComment()
&& ($ce
&& (!$ce->isRegistrationOpen()
|| ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))))
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"statusApprovedForClosedCalendarEvent" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS
|| !$ce || ($student && $student->isCandidateReviewIsNotRequired())
|| $ce->isRegistrationOpen()
|| $status == Status::STATUS_APPROVED,
"comment" => !!$candidate->getComment() ||
($student && $student->isCandidateReviewIsNotRequired())
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"curator" => !!$candidate->getCurator(),
"calendarEventsParticipations" => !$isCalendarEventsParticipationsRequired
|| ($missingRequiredCandidateCalendarEvents !== null
&& count($missingRequiredCandidateCalendarEvents) === 0),
"deniedCalendarEventsParticipations" => !$isDeniedCalendarEventParticipationCheckRequired
|| ($deniedCalendarEventsParticipations !== null
&& count($deniedCalendarEventsParticipations) === 0),
"levelCompliance" => !$ce || $ce->getFromLevel() === null || !$person
|| ($student && $student->getLevel() >= $ce->getFromLevel()
&& ($ce->getToLevel() === null || $student->getLevel() <= $ce->getToLevel())),
"studentStatusCompliance" => !$ce || $ce->getRequireStudentStatus() === null || !$person
|| ($person->getVirtualStudentStatus() == $ce->getRequireStudentStatus()),
];
$fieldTexts = [
"person" => "ФИО",
"calendarEvent" => "мероприятие",
"image" => "фото",
"reviewApproveComment" => "комментарий согласования о пройденном отборе",
"statusApprovedForClosedCalendarEvent" =>
"статус должен быть \"" . Status::getText(Status::STATUS_APPROVED)
. "\" для закрытой регистрации на мероприятие",
"comment" => "комментарий",
"curator" => "куратор",
"calendarEventsParticipations" => "отсуствует участие в обязательных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $missingRequiredCandidateCalendarEvents ?? [])),
"deniedCalendarEventsParticipations" => "есть участие в исключенных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $deniedCalendarEventsParticipations ?? [])),
"levelCompliance" => ($ce ? "несоответствие уровня участника: "
. 'необходим ' . $ce->getFromLevel() . " ур." : ""),
"studentStatusCompliance" => ($ce ? "несоответствие статуса участника: "
. 'необходим ' . $ce->getRequireStudentStatusText() . " ур." : ""),
];
foreach ($isFieldNotRequired as $fieldName => $isSet) {
if (!$isSet) {
$result['needFields'][] = [
"field" => $fieldName,
"text" => $fieldTexts[$fieldName],
];
}
}
$allRequiredFieldsSet = true;
foreach ($requiredKeys as $key) {
if (!$isFieldNotRequired[$key]) {
$allRequiredFieldsSet = false;
break;
}
}
if ($allRequiredFieldsSet
// && $candidate->getCurator()
) {
if ($ce &&
(!$ce->isIsCandidateReviewRequired() || $ce->isFinished()
|| ($student && $student->isCandidateReviewIsNotRequired()))) {
$this->setStatusApproved($candidate);
} else {
$this->setStatus($candidate, $status);
}
$result['result'] = true;
}
return $result;
}
/**
* @param Candidate $candidate
* @param $status
* @param bool $checkPhoto
* @return array{result: bool, needFields: array, candidate: Candidate}
*/
public function isCandidateCorrespondToCalendarEvent(Candidate $candidate,
bool $checkPhoto = true): array
{
$result = [
"result" => false,
"needFields" => [],
"candidate" => $candidate,
];
$ce = $candidate->getCalendarEvent();
$person = $candidate->getPerson();
$student = $person ? $person->getStudent() : null;
$isCeFinished = $ce
&& $ce->isFinished();
$isCeCandidateReviewRequired = $ce
&& $ce->isIsCandidateReviewRequired();
$isCalendarEventsParticipationsRequired = $ce
&& $ce->getRequireCalendarEventsParticipations()->count();
$isDeniedCalendarEventParticipationCheckRequired = $ce
&& $ce->getDenyCalendarEventsParticipations()->count();
$isRegistrationOpen = $ce
&& $ce->isRegistrationOpen();
$isStatusApproved = $candidate->getStatus() == Status::STATUS_APPROVED;
$isCalendarEventTypeRequiresApproveReviewComment = $ce
&& Type::isApproveReviewCommentRequireType($ce->getType());
/** @var CalendarEvent[] $missingRequiredCandidateCalendarEvents */
$missingRequiredCandidateCalendarEvents = null;
/** @var CalendarEvent[] $deniedCalendarEventsParticipations */
$deniedCalendarEventsParticipations = null;
$requiredKeys = ['person', 'calendarEvent', "comment", "curator", "levelCompliance",
"studentStatusCompliance"];
if ($person && (!$person->getStudent()
|| $person->getStudent()->getLevel() <= 1)) { //todo change this hardcode. Добавить 1-й так же без комментария
$requiredKeys = array_filter($requiredKeys, function ($key) {
return $key != "comment";
});
}
if ((!$isRegistrationOpen || ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))
&& !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'reviewApproveComment';
}
if (!$isRegistrationOpen && !CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS) {
$requiredKeys[] = 'statusApprovedForClosedCalendarEvent';
}
if ($checkPhoto && $ce && $ce->isIsCandidateReviewRequired()
&& (!$student || !$student->isCandidateReviewIsNotRequired())) {
$requiredKeys[] = 'image';
}
if ($isCalendarEventsParticipationsRequired) {
$requiredKeys[] = 'calendarEventsParticipations';
if ($ce && $person) {
$missingRequiredCandidateCalendarEvents =
$this->calendarEventService->getMissingRequiredCandidateCalendarEvents(
$person, $ce
);
}
}
if ($isDeniedCalendarEventParticipationCheckRequired) {
$requiredKeys[] = 'deniedCalendarEventsParticipations';
if ($ce && $person) {
$deniedCalendarEventsParticipations =
$this->calendarEventService->getDeniedCalendarEventsForPerson(
$person, $ce
);
}
}
$isFieldNotRequired = [
"person" => !!$person,
"calendarEvent" => $ce ? true : false,
"image" => !$checkPhoto || $status == Status::STATUS_APPROVED
|| ((($isCeFinished || $isCeCandidateReviewRequired)) && $candidate->getImage())
|| ($student && $student->isCandidateReviewIsNotRequired()) ? true : false,
"reviewApproveComment" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS || !(!$candidate->getReviewApproveComment()
&& ($ce
&& (!$ce->isRegistrationOpen()
|| ($isStatusApproved && $isCalendarEventTypeRequiresApproveReviewComment))))
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"statusApprovedForClosedCalendarEvent" => CalendarEventService::FORCE_REGISTER_CLOSED_EVENTS
|| !$ce || ($student && $student->isCandidateReviewIsNotRequired())
|| $ce->isRegistrationOpen()
|| $status == Status::STATUS_APPROVED,
"comment" => !!$candidate->getComment() ||
($student && $student->isCandidateReviewIsNotRequired())
|| ($ce && !$ce->isIsCandidateReviewRequired())
,
"curator" => !!$candidate->getCurator(),
"calendarEventsParticipations" => !$isCalendarEventsParticipationsRequired
|| ($missingRequiredCandidateCalendarEvents !== null
&& count($missingRequiredCandidateCalendarEvents) === 0),
"deniedCalendarEventsParticipations" => !$isDeniedCalendarEventParticipationCheckRequired
|| ($deniedCalendarEventsParticipations !== null
&& count($deniedCalendarEventsParticipations) === 0),
"levelCompliance" => !$ce || $ce->getFromLevel() === null || !$person
|| ($student && $student->getLevel() >= $ce->getFromLevel()
&& ($ce->getToLevel() === null || $student->getLevel() <= $ce->getToLevel())),
"studentStatusCompliance" => !$ce || $ce->getRequireStudentStatus() === null || !$person
|| ($person->getVirtualStudentStatus() == $ce->getRequireStudentStatus()),
];
$fieldTexts = [
"person" => "ФИО",
"calendarEvent" => "мероприятие",
"image" => "фото",
"reviewApproveComment" => "комментарий согласования о пройденном отборе",
"statusApprovedForClosedCalendarEvent" =>
"статус должен быть \"" . Status::getText(Status::STATUS_APPROVED)
. "\" для закрытой регистрации на мероприятие",
"comment" => "комментарий",
"curator" => "куратор",
"calendarEventsParticipations" => "отсуствует участие в обязательных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $missingRequiredCandidateCalendarEvents ?? [])),
"deniedCalendarEventsParticipations" => "есть участие в исключенных мероприятиях: "
. implode(", ", array_map(function ($e) {
return mb_lcfirst($e->getNameText(), 'utf-8') . " (" . $e->getStartDate()->format("d.m.Y") . ")";
}, $deniedCalendarEventsParticipations ?? [])),
"levelCompliance" => ($ce ? "несоответствие уровня участника: "
. 'необходим ' . $ce->getFromLevel() . " ур." : ""),
"studentStatusCompliance" => ($ce ? "несоответствие статуса участника: "
. 'необходим ' . $ce->getRequireStudentStatusText() . " ур." : ""),
];
foreach ($isFieldNotRequired as $fieldName => $isSet) {
if (!$isSet) {
$result['needFields'][] = [
"field" => $fieldName,
"text" => $fieldTexts[$fieldName],
];
}
}
$allRequiredFieldsSet = true;
foreach ($requiredKeys as $key) {
if (!$isFieldNotRequired[$key]) {
$allRequiredFieldsSet = false;
break;
}
}
if ($allRequiredFieldsSet
// && $candidate->getCurator()
) {
if ($ce &&
(!$ce->isIsCandidateReviewRequired() || $ce->isFinished()
|| ($student && $student->isCandidateReviewIsNotRequired()))) {
$this->setStatusApproved($candidate);
} else {
$this->setStatus($candidate, $status);
}
$result['result'] = true;
}
return $result;
}
/**
* @param Person $person
* @param CalendarEvent $calendarEvent
* @param User $author
* @return array{result: bool, needFields: array, candidate: Candidate}
*/
public function createApprovedCandidate(Person $person, CalendarEvent $calendarEvent, User $author): array
{
$candidate = (new Candidate())
->setPerson($person)
->setCalendarEvent($calendarEvent)
->setStatus(Status::STATUS_DRAFT)
->setAuthor($author);
if ($this->existsSame($candidate)) {
return [
"result" => false,
"needFields" => [],
"candidate" => null,
];
}
$result = $this->confirmCandidate($candidate, Status::STATUS_APPROVED, false);
if ($result['result']) {
if ($candidate->getPerson()->getStudent() && $candidate->getPerson()->getStudent()->isCandidateReviewIsNotRequired()) {
$candidate->setStatus(Status::STATUS_APPROVED);
}
$this->em->persist($candidate);
$this->em->flush();
}
return $result;
}
/**
* @param Person[] $persons
* @return array{int, Candidate|null}[]
*/
public function getPersonsLastCandidateWithEarnLevelCalendarEvent(array $persons): array
{
$result = [];
foreach ($persons as $person) {
$qb = $this->em->createQueryBuilder();
$qb->select('c')
->from(Candidate::class, 'c')
->join('c.calendarEvent', 'ce')
->where('c.person = :person')
->andWhere('ce.earnLevel IS NOT NULL')
->andWhere('ce.earnLevel >= 0')
->andWhere($qb->expr()->in('c.status', ':statuses'))
->andWhere('ce.endDate <= :today')
->setParameter('today', (new \DateTime())->setTime(0,0,0))
->setParameter('statuses', Status::getForLastCandidateWithEarnLevelCalendarEventStatuses())
->setParameter('person', $person)
->orderBy('ce.startDate', 'DESC')
->setMaxResults(1);
$lastEvent = $qb->getQuery()->getOneOrNullResult();
$result[$person->getId()] = $lastEvent;
}
return $result;
}
public function getPersonsLastCandidate(array $persons): array
{
$result = [];
foreach ($persons as $person) {
$qb = $this->em->createQueryBuilder();
$qb->select('c')
->from(Candidate::class, 'c')
->where('c.person = :person')
->andWhere($qb->expr()->in('c.status', ':statuses'))
->setParameter('statuses', Status::getForLastCandidateStatuses())
->setParameter('person', $person)
->orderBy('c.createdAt', 'DESC')
->setMaxResults(1);
$lastCandidate = $qb->getQuery()->getOneOrNullResult();
$result[$person->getId()] = $lastCandidate;
}
return $result;
}
public function getCandidateByCalendarEvent(Person $person, ?int $calendarEventId,
&$calendarEvent): ?Candidate
{
if ($calendarEventId !== null) {
$calendarEvent = $this->calendarEventService->getBaseService()->get($calendarEventId);
}
if (!$calendarEvent) {
return null;
}
$candidate = $this->getFirst([
'person' => $person,
'calendarEvent' => $calendarEvent,
]);
return $candidate;
}
/**
* @param Person $person
* @return array|Candidate[]
*/
public function getUpcomingCalendarEventCandidates(Person $person,
array $orderCalendarEventsBy = ['startDate', 'ASC']): array
{
$qb = $this->em->createQueryBuilder();
$qb->select('c')
->from(Candidate::class, 'c')
->join('c.calendarEvent', 'ce')
->where('c.person = :person')
->andWhere('ce.startDate > :today')
->setParameter('today', new \DateTime())
->setParameter('person', $person);
if ($orderCalendarEventsBy) {
$qb->orderBy('ce.' . $orderCalendarEventsBy[0], $orderCalendarEventsBy[1]);
}
return $qb->getQuery()->getResult();
}
/**
* @param array|Invoice[] $invoices
* @return array|Candidate[]
*/
public function getByInvoices(array $invoices): array
{
$candidates = [];
foreach ($invoices as $invoice) {
$candidate = $invoice->getCandidate();
$candidates[$invoice->getId()] = $candidate;
}
return $candidates;
}
public function sortByCalendarEventStartDate(array &$candidates): void
{
usort($candidates, function (Candidate $a, Candidate $b) {
$aStartDate = $a->getCalendarEvent() ? $a->getCalendarEvent()->getStartDate() : null;
$bStartDate = $b->getCalendarEvent() ? $b->getCalendarEvent()->getStartDate() : null;
if ($aStartDate == $bStartDate) {
return 0;
}
if ($aStartDate === null) {
return 1;
}
if ($bStartDate === null) {
return -1;
}
return $aStartDate < $bStartDate ? -1 : 1;
});
}
/**
* @param array|Candidate[] $candidates
* @return array{int, Candidate[]}
*/
public function groupByFinanceCategoryId(array $candidates): array
{
$result = [];
foreach ($candidates as $candidate) {
$financeCategory = $candidate->getCalendarEvent() ? $candidate->getCalendarEvent()->getFinanceCategory() : null;
$financeCategory = $financeCategory ? $financeCategory->getId() : -1;
if (!isset($result[$financeCategory])) {
$result[$financeCategory] = [];
}
$result[$financeCategory][] = $candidate;
}
return $result;
}
/**
* @param Person $person
* @return array|Candidate[]
*/
public function getCandidates(Person $person): array
{
return $this->getDefault([
"person" => $person,
]);
}
}