src/Service/CalendarEvent/CalendarEventService.php line 253

Open in your IDE?
  1. <?php
  2. namespace App\Service\CalendarEvent;
  3. use App\Entity\CalendarEvent;
  4. use App\Entity\Payment;
  5. use App\Entity\Person;
  6. use App\Enum\CalendarEvent\Type;
  7. use App\Service\CalendarEventKind\CalendarEventKindService;
  8. use App\Service\Candidate\CandidateService;
  9. use App\Service\ServiceRetriever;
  10. use App\Service\User\UserService;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. use App\Service\BaseEntityService;
  13. class CalendarEventService extends BaseEntityService
  14. {
  15.     const FORCE_REGISTER_CLOSED_EVENTS true;
  16.     /**
  17.      * @var ServiceRetriever
  18.      */
  19.     protected $serviceRetriever;
  20.     /**
  21.      * @var CalendarEventKindService
  22.      */
  23.     private $calendarEventKindService;
  24.     /**
  25.      * @var UserService
  26.      */
  27.     private $userService;
  28.     public function __construct(EntityManagerInterface   $emServiceRetriever $serviceRetriever,
  29.                                 CalendarEventKindService $calendarEventKindServiceUserService $userService)
  30.     {
  31.         parent::__construct($em);
  32.         $this->initialize(CalendarEvent::class);
  33.         $this->serviceRetriever $serviceRetriever;
  34.         $this->calendarEventKindService $calendarEventKindService;
  35.         $this->userService $userService;
  36.     }
  37.     public function getPaymentChoices(Payment $payment): array
  38.     {
  39.         $userSettings $this->userService->getLoggedInUserSettings();
  40.         $start $userSettings $userSettings->getStartCalendarEventsDate() : null;
  41.         $end $userSettings $userSettings->getEndCalendarEventsDate() : null;
  42.         $items $this->getForAccountantCalendarEvents();
  43.         if ($start && $end) {
  44.             $items $this->filterByEndDate($items$start$end);
  45.         }
  46.         return $items;
  47.     }
  48.     public function getChoiceLabel(CalendarEvent $calendarEvent$entity): string
  49.     {
  50.         $class get_class($entity);
  51.         switch ($class) {
  52.             case Payment::class:
  53.                 return $calendarEvent->getNameWithCode();
  54.             case CalendarEvent::class:
  55.                 return $calendarEvent->getNameText() . ' (' $calendarEvent->getStartDate()->format('d.m.Y') . ')';
  56.             default:
  57.                 return $calendarEvent->getStartDate()->format("d.m.Y") . " – " $calendarEvent->getNameText();
  58.         }
  59.     }
  60.     /**
  61.      * @return CalendarEvent[]
  62.      */
  63.     public function getByPrice(int $price): array
  64.     {
  65.         return $this->getDefault([
  66.             "price" => $price,
  67.         ]);
  68.     }
  69.     public function getCandidateChoices(): array
  70.     {
  71.         return $this->getAll();
  72.     }
  73.     /**
  74.      * Возвращает возможные варианты CalendarEvent для поля requireCalendarEventsParticipations
  75.      * Исключаем текущее редактируемое событие из списка, если оно уже сохранено (имеет id)
  76.      * @param CalendarEvent|null $excludeCalendarEvent
  77.      * @return CalendarEvent[]
  78.      */
  79.     public function getCalendarEventChoices(?CalendarEvent $excludeCalendarEvent null): array
  80.     {
  81.         $all $this->getDefault(nullnull,
  82.             nullnull,
  83.             null, ["startDate""DESC"]);
  84.         if (!$excludeCalendarEvent || !$excludeCalendarEvent->getId()) {
  85.             return $all;
  86.         }
  87.         return array_values(array_filter($all, function (CalendarEvent $event) use ($excludeCalendarEvent) {
  88.             return $event->getId() !== $excludeCalendarEvent->getId();
  89.         }));
  90.     }
  91.     public function getCalendarEventByDate(\DateTime $now): ?CalendarEvent
  92.     {
  93.         // 1) Попробуем найти событие, которое покрывает текущую дату (startDate <= today <= endDate)
  94.         $today = (clone $now)->setTime(000);
  95.         // 2) Если не найдено, ищем все события, которые в рамках даты today (startDate<=today<=endDate)
  96.         //    и у которых время начала/окончания попадает в интервал now-2h .. now+2h. Выберем ближайшее по времени к now.
  97.         $from = (clone $now)->modify('-1 hours');
  98.         $to = (clone $now)->modify('+1 hours');
  99.         $fromTime $from->format('H:i:s');
  100.         $toTime $to->format('H:i:s');
  101.         $q2 $this->em->getRepository(CalendarEvent::class)->createQueryBuilder('e');
  102.         $q2->andWhere('e.startDate <= :today')
  103.             ->andWhere('e.endDate >= :today')
  104.             ->andWhere('(' .
  105.                 "(e.startTime BETWEEN :fromTime AND :toTime) OR " .
  106.                 "(e.endTime BETWEEN :fromTime AND :toTime) OR " .
  107.                 "(e.startTime <= :fromTime AND e.endTime >= :toTime)" .
  108.             ')')
  109.             ->setParameter('today'$today->format('Y-m-d'))
  110.             ->setParameter('fromTime'$fromTime)
  111.             ->setParameter('toTime'$toTime);
  112.         $events $q2->getQuery()->getResult();
  113.         if (count($events) === 0) {
  114.             return null;
  115.         }
  116.         $closest null;
  117.         $closestDiff null;
  118.         foreach ($events as $event) {
  119.             /** @var CalendarEvent $event */
  120.             $eventStartTime $event->getStartTime();
  121.             if (!$eventStartTime) {
  122.                 continue;
  123.             }
  124.             // Создаём datetime для сегодняшнего события, комбинируя сегодняшнюю дату и время начала события
  125.             $eventStart = new \DateTime($now->format('Y-m-d') . ' ' $eventStartTime->format('H:i:s'));
  126.             $diff abs($eventStart->getTimestamp() - $now->getTimestamp());
  127.             if ($closest === null || $diff $closestDiff) {
  128.                 $closest $event;
  129.                 $closestDiff $diff;
  130.             }
  131.         }
  132.         return $closest;
  133.     }
  134.     /**
  135.      * @param mixed $telegramCampaign
  136.      * @return CalendarEvent[]
  137.      */
  138.     public function getTelegramCampaignChoices($telegramCampaign null): array
  139.     {
  140.         $this->calendarEventService $this->serviceRetriever->getService(CalendarEventService::class);
  141.         return $this->calendarEventService->getAll();
  142.     }
  143.     public function getByStartEndDate(\DateTime $start null\DateTime $end null,
  144.           $order = ["startDate""DESC"]): array
  145.     {
  146.         $qb $this->em->getRepository(CalendarEvent::class)->createQueryBuilder('e');
  147.         if ($start) {
  148.             $qb->andWhere('e.startDate >= :start')
  149.                 ->setParameter('start'$start->format('Y-m-d'));
  150.         }
  151.         if ($end !== null) {
  152.             $qb->andWhere('e.endDate <= :end')
  153.                 ->setParameter('end'$end->format('Y-m-d'));
  154.         }
  155.         if ($order) {
  156.             $qb->orderBy('e.' $order[0], $order[1]);
  157.         }
  158.         return $qb->getQuery()->getResult();
  159.     }
  160.     /**
  161.      * @return array|CalendarEvent[]
  162.      */
  163.     public function getCalendarEventsForMakeCandidates($type null$order null): array
  164.     {
  165.         return $this->getCalendarEventsForRegister($type,
  166.             $order, -7);
  167.     }
  168.     /**
  169.      * @return array|CalendarEvent[]
  170.      */
  171.     public function getCalendarEventsForRegister($type null$order null,
  172.          $addDaysToStartDate null): array
  173.     {
  174.         if ($type && !is_array($type)) {
  175.             $type = [$type];
  176.         }
  177.         $start = new \DateTime();
  178.         if ($addDaysToStartDate) {
  179.             $start $start->modify("$addDaysToStartDate days");
  180.         }
  181.         $result $this->getByStartEndDate($startnull$order);
  182.         $result array_filter($result, function (CalendarEvent $event) use ($start$type$addDaysToStartDate) {
  183.             return !$event->isStarted(($addDaysToStartDate ?? 0) * -1) &&
  184.                 ($type === null || in_array($event->getType(), $type));
  185.         });
  186.         if ($type && in_array(Type::TYPE_PERSONAL_CONSULTATION$type)) {
  187.             $result2 $this->getDefault([
  188.                 "endDate" => ["startDate" => $start->format("Y-m-d")],
  189.             ]);
  190.             $result2 array_filter($result2, function (CalendarEvent $event) use ($start$type$addDaysToStartDate) {
  191.                 return ($type === null || in_array($event->getType(), [Type::TYPE_PERSONAL_CONSULTATION]));
  192.             });
  193.             $result array_merge($result$result2);
  194.             $result array_unique($result);
  195.         }
  196.         return $result;
  197.     }
  198.     public function getCalendarEventsForChannelApprove($type null$order = ["startDate""ASC"]): array
  199.     {
  200.         if ($type && !is_array($type)) {
  201.             $type = [$type];
  202.         }
  203.         $start = new \DateTime();
  204.         $result $this->getByStartEndDate($startnull$order);
  205.         return $result;
  206.     }
  207.     public function getAll(array $orderBy null$type null\DateTime $start null,
  208.                        \DateTime $end null$status null): array
  209.     {
  210.         if ($orderBy === null) {
  211.             $orderBy = ["startDate""DESC"];
  212.         }
  213.         $params = [];
  214.         if ($type !== null) {
  215.             $params['type'] = $type;
  216.         }
  217.         if ($start !== null) {
  218.             $params['startDate'] = ["startDate" => $start->format("Y-m-d"),
  219.                 "endDate" => $end->format("Y-m-d")];
  220.         }
  221.         if ($status !== null) {
  222.             $params['status'] = $status;
  223.         }
  224.         return $this->getDefault($paramsnullnull,
  225.             nullnull$orderBy);
  226.     }
  227.     /**
  228.      * @return array|CalendarEvent[]
  229.      */
  230.     public function getForDropDown($start$end): array
  231.     {
  232.         return $this->getAll(["startDate""ASC"],
  233.             Type::getDropDownTypes(), $start$end,
  234.             \App\Enum\CalendarEvent\Status::getForListStatuses());
  235.     }
  236.     public function getClosestCalendarEvent(): ?CalendarEvent
  237.     {
  238.         $now = new \DateTime();
  239.         // current
  240.         $qb $this->em->getRepository(CalendarEvent::class)->createQueryBuilder('e');
  241.         $qb->andWhere('e.startDate >= :now')
  242.             ->andWhere('e.endDate >= :now')
  243.             ->setParameter('now'$now->format('Y-m-d'))
  244.             ->orderBy('e.startDate''ASC')
  245.             ->setMaxResults(1);
  246.         $currentEvents $qb->getQuery()->getResult();
  247.         if (count($currentEvents) > 0) {
  248.             return $currentEvents[0];
  249.         }
  250.         // ищем ближайшее будущее событие
  251.         $qb $this->em->getRepository(CalendarEvent::class)->createQueryBuilder('e');
  252.         $qb->andWhere('e.startDate > :now')
  253.             ->setParameter('now'$now->format('Y-m-d'))
  254.             ->orderBy('e.startDate''ASC')
  255.             ->setMaxResults(1);
  256.         $futureEvents $qb->getQuery()->getResult();
  257.         if (count($futureEvents) > 0) {
  258.             return $futureEvents[0];
  259.         }
  260.         //or get last past event
  261.         $last $this->getFirst([], nullnull,
  262.             null, ["startDate""DESC"]);
  263.         if ($last) {
  264.             return $last;
  265.         }
  266.         return null;
  267.     }
  268.     public function getMissingRequiredCandidateCalendarEvents(Person $person,
  269.         CalendarEvent $calendarEvent): array
  270.     {
  271.         if (!$calendarEvent->getRequireCalendarEventsParticipations()->count()) {
  272.             return [];
  273.         }
  274.         $candidateService $this->serviceRetriever->getService(CandidateService::class);
  275.         /** @var \App\Entity\Candidate[] $candidates */
  276.         $candidates $candidateService->getDefault([
  277.             'person' => $person,
  278.             'status' => \App\Enum\Candidate\Status::STATUS_APPROVED,
  279.         ]);
  280.         $missingEvents = [];
  281.         foreach ($calendarEvent->getRequireCalendarEventsParticipations() as $requiredEvent) {
  282.             $found false;
  283.             foreach ($candidates as $candidate) {
  284.                 if ($candidate->getCalendarEvent()->getId() === $requiredEvent->getId()) {
  285.                     $found true;
  286.                     break;
  287.                 }
  288.             }
  289.             if (!$found) {
  290.                 $missingEvents[] = $requiredEvent;
  291.             }
  292.         }
  293.         return $missingEvents;
  294.     }
  295.     public function getDeniedCalendarEventsForPerson(Person $personCalendarEvent $calendarEvent): array
  296.     {
  297.         if (!$calendarEvent->getDenyCalendarEventsParticipations()->count()) {
  298.             return [];
  299.         }
  300.         $candidateService $this->serviceRetriever->getService(CandidateService::class);
  301.         /** @var \App\Entity\Candidate[] $candidates */
  302.         $candidates $candidateService->getDefault([
  303.             'person' => $person,
  304.             'status' => \App\Enum\Candidate\Status::STATUS_APPROVED,
  305.         ]);
  306.         $deniedEvents = [];
  307.         foreach ($calendarEvent->getDenyCalendarEventsParticipations() as $deniedEvent) {
  308.             foreach ($candidates as $candidate) {
  309.                 if ($candidate->getCalendarEvent()->getId() === $deniedEvent->getId()) {
  310.                     $deniedEvents[] = $deniedEvent;
  311.                     break;
  312.                 }
  313.             }
  314.         }
  315.         return $deniedEvents;
  316.     }
  317.     /**
  318.      * @param array $ids
  319.      * @return CalendarEvent[]
  320.      */
  321.     public function getByIds(array $ids): array
  322.     {
  323.         return $this->getDefault([
  324.             'id' => $ids,
  325.         ]);
  326.     }
  327.     /**
  328.      * @return array|CalendarEvent[]
  329.      */
  330.     public function getForAccountantCalendarEvents(): array
  331.     {
  332.         /** @var CalendarEvent[] $calendarEvents */
  333.         $calendarEvents $this->getAll(["startDate""ASC"]);
  334.         $calendarEvents array_filter($calendarEvents, function ($event) {
  335.             return $event->getPrice();
  336.         });
  337.         $minEndDate = (new \DateTime())->modify("-2 month");
  338.         $calendarEvents array_filter($calendarEvents, function (CalendarEvent $ce) use ($minEndDate) {
  339.             if ($ce->getConnectingCalendarEvent() && $ce->getConnectingCalendarEvent()->isConnectingEventForAccountant()) {
  340.                 return false;
  341.             }
  342.             if ($ce->getEndDate() < $minEndDate) {
  343.                 return false;
  344.             }
  345.             return true;
  346.         });
  347.         return $calendarEvents;
  348.     }
  349.     public function filterByEndDate(array $items\DateTime $start\DateTime $end): array
  350.     {
  351.         return array_filter($items, function (CalendarEvent $item) use ($start$end) {
  352.             return $item->getEndDate() >= $start && $item->getEndDate() <= $end;
  353.         });
  354.     }
  355.     /**
  356.      * @param array $row
  357.      * @param CalendarEvent[] $calendarEvents
  358.      * @param array $purposeRegexes
  359.      * @param int $amountColIndex
  360.      * @param string|null $firstName
  361.      * @param string|null $lastName
  362.      */
  363.     public function getCalendarEventFromBankStatementRow(array $row,
  364.          array $calendarEvents, array $purposeRegexes,
  365.          int $amountColIndex,
  366.          string $firstName nullstring $lastName null,
  367.          array $requiredDataMatchNames null): array
  368.     {
  369.         $result = [
  370.             "calendarEvent" => null,
  371.             "row" => null,
  372.         ];
  373.         $findName $firstName || $lastName;
  374.         $requiredDataMatchNames array_merge([
  375.             "name" => $findName,
  376.             "amount" => false,
  377.             "purpose" => false,
  378.         ], $requiredDataMatchNames ?? []);
  379.         $isMatchesFound = [];
  380.         foreach ($requiredDataMatchNames as $name => $isRequired) {
  381.             $isMatchesFound[$name] = false;
  382.         }
  383.         if ($findName) {
  384.             $firstName mb_strtolower(mb_trim($firstNamenull'UTF-8'), 'UTF-8');
  385.             $lastName mb_strtolower(mb_trim($lastNamenull'UTF-8'), 'UTF-8');
  386.             $nameVars = [
  387.                 $firstName " " $lastName,
  388.                 $lastName " " $firstName,
  389.             ];
  390.             $rowText implode("|||"$row);
  391.             $rowText mb_strtolower($rowText'UTF-8');
  392.             foreach ($nameVars as $nameVar) {
  393.                 if (mb_strpos($rowText$nameVar) !== false) {
  394.                     $isMatchesFound["name"] = true;
  395.                     break;
  396.                 }
  397.             }
  398.             if (!$isMatchesFound["name"] && $requiredDataMatchNames["name"]) {
  399.                 return $result;
  400.             }
  401.         }
  402.         $amount = (int)$row[$amountColIndex];
  403.         foreach ($calendarEvents as $calendarEvent) {
  404.             $isMatchesFound["amount"] = $calendarEvent->getPrice() === $amount;
  405.             if (!isset($purposeRegexes["calendar_event_" $calendarEvent->getId()])) {
  406.                 throw new \Exception("No purpose regex found for calendar event: " $calendarEvent->getName());
  407.             }
  408.             $purposeRegex $purposeRegexes["calendar_event_" $calendarEvent->getId()];
  409.             $isPurposeFound false;
  410.             foreach ($row as $cell) {
  411.                 $cell mb_strtolower($cell'UTF-8');
  412.                 if (preg_match($purposeRegex$cell)) {
  413.                     $isPurposeFound true;
  414.                     break;
  415.                 }
  416.             }
  417.             $isMatchesFound["purpose"] = $isPurposeFound;
  418.             $isAllRequiredMatchesFound true;
  419.             foreach ($requiredDataMatchNames as $name => $isRequired) {
  420.                 if ($isRequired && !$isMatchesFound[$name]) {
  421.                     $isAllRequiredMatchesFound false;
  422.                     break;
  423.                 }
  424.             }
  425.             if ($isAllRequiredMatchesFound) {
  426.                 $result["calendarEvent"] = $calendarEvent;
  427.                 $result["row"] = $row;
  428.                 return $result;
  429.             }
  430.         }
  431.         return $result;
  432.     }
  433.     public function checkCalendarEventNameRegex(string $regex): bool
  434.     {
  435.         $calendarEvents $this->getDefault();
  436.         $calendarEvents array_filter($calendarEvents, function (CalendarEvent $calendarEvent) {
  437.             return !!$calendarEvent->getPrice();
  438.         });
  439.         $calendarEvents array_filter($calendarEvents, function (CalendarEvent $calendarEvent) use ($regex) {
  440.             $calendarEventName $calendarEvent->getName();
  441.             $calendarEventName mb_strtolower($calendarEventName'UTF-8');
  442.             $result preg_match($regex$calendarEventName);
  443.             return $result;
  444.         });
  445.         $calendarEvents array_values($calendarEvents);
  446.         $matchedCalendarEventNamesCount count($calendarEvents);
  447.         if ($matchedCalendarEventNamesCount 1) {
  448.            $isAllEventsSameKind $this->calendarEventKindService
  449.                ->isAllCalendarEventsOfSameKind($calendarEvents);
  450.             if (!$isAllEventsSameKind) {
  451.                 throw new \Exception("Multiple calendar events matched regex \"$regex\" and they are of different kinds");
  452.             }
  453.             return true;
  454.         } elseif ($matchedCalendarEventNamesCount == 0) {
  455.             throw new \Exception("No calendar events matched regex \"$regex\"");
  456.         } elseif ($matchedCalendarEventNamesCount == 1) {
  457.             if (!$calendarEvents[0]->getKind()) {
  458.                 throw new \Exception("Matched calendar event does not have a kind");
  459.             }
  460.             return true;
  461.         }
  462.         return false;
  463.     }
  464.     public function checkCalendarEventsReadyForBankStatementWorkWith(array $calendarEvents)
  465.     {
  466.         foreach ($calendarEvents as $calendarEvent) {
  467.             foreach ($calendarEvents as $calendarEvent2) {
  468.                 if ($calendarEvent->getId() !== $calendarEvent2->getId()
  469.                     && $calendarEvent->getPrice() === $calendarEvent2->getPrice()) {
  470.                     throw new \Exception("Calendar events \"{$calendarEvent->getName()}\" and \"{$calendarEvent2->getName()}\" have the same price {$calendarEvent->getPrice()}. All calendar events should have different price for bank statement work");
  471.                 }
  472.             }
  473.         }
  474.     }
  475.     /**
  476.      * @param string $names
  477.      * @return CalendarEvent[]
  478.      */
  479.     public function getByNames(array $names): array
  480.     {
  481.         $q $this->em->getRepository(CalendarEvent::class)->createQueryBuilder('item');
  482.         $q
  483.             ->andWhere('item.name in (:name)')
  484.             ->setParameter('name'$names)
  485.         ;
  486.         $ces $q->getQuery()->getResult();
  487.         return $ces;
  488.     }
  489.     public function sortItemsByCalendarEvent(array &$itemsbool $asc true)
  490.     {
  491.         usort($items, function ($a$b) use ($asc) {
  492.             $aDate $a->getCalendarEvent() ? $a->getCalendarEvent()->getStartDate() : null;
  493.             $bDate $b->getCalendarEvent() ? $b->getCalendarEvent()->getStartDate() : null;
  494.             if ($aDate === null && $bDate === null) {
  495.                 return 0;
  496.             } elseif ($aDate === null) {
  497.                 return 1;
  498.             } elseif ($bDate === null) {
  499.                 return -1;
  500.             } else {
  501.                 return $asc $aDate <=> $bDate $bDate <=> $aDate;
  502.             }
  503.         });
  504.     }
  505.     /**
  506.      * @return array|CalendarEvent[]
  507.      */
  508.     public function getForBankStatementDataImport(): array
  509.     {
  510.         /** @var CalendarEvent[] $calendarEventsForRegister */
  511.         $calendarEventsForRegister $this->getAll(
  512.             ["startDate""DESC"], Type::getForRegisterTypes()
  513.         );
  514.         $calendarEventsForRegister array_filter($calendarEventsForRegister, function (CalendarEvent $ce) {
  515.             return !$ce->getConnectingCalendarEvent() && $ce->getPrice();
  516.         });
  517.         return array_values($calendarEventsForRegister);
  518.     }
  519.     public function hasVTPaymentRequisites(?CalendarEvent $calendarEvent): bool
  520.     {
  521.         if (!$calendarEvent || !$calendarEvent->getPaymentRequisites()) {
  522.             return false;
  523.         }
  524.         return $calendarEvent->getPaymentRequisites()->getTitle() == "ИП Вычужанина Татьяна";
  525.     }
  526.     public function isFutureCalendarEvent(?CalendarEvent $calendarEvent): bool
  527.     {
  528.         if (!$calendarEvent) {
  529.             return false;
  530.         }
  531.         $now = new \DateTime();
  532.         return $now $calendarEvent->getStartDate();
  533.     }
  534. }