src/Entity/CRM/LeaveRequest.php line 19

  1. <?php
  2. namespace App\Entity\CRM;
  3. use App\Repository\LeaveRequestRepository;
  4. use DateTime;
  5. use Doctrine\Common\Collections\ArrayCollection;
  6. use Doctrine\Common\Collections\Collection;
  7. use Doctrine\DBAL\Types\Types;
  8. use Doctrine\ORM\Mapping as ORM;
  9. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  10. use Symfony\Component\Validator\Constraints as Assert;
  11. use App\Validator\Constraints as AppAssert;
  12. #[AppAssert\NoLeaveRequestIfWorked]
  13. #[AppAssert\NoOverlappingLeaveRequest]
  14. #[AppAssert\EnoughLeaveBalance]
  15. #[ORM\Entity(repositoryClassLeaveRequestRepository::class)]
  16. class LeaveRequest
  17. {
  18.     #[ORM\Id]
  19.     #[ORM\GeneratedValue]
  20.     #[ORM\Column]
  21.     private ?int $id null;
  22.     #[ORM\ManyToOne(inversedBy'leaveRequests')]
  23.     #[ORM\JoinColumn(nullabletrue)]
  24.     private ?Consultant $consultant null;
  25.     #[ORM\Column(typeTypes::DATETIME_MUTABLE)]
  26.     private ?\DateTimeInterface $startTime null;
  27.     #[ORM\Column(typeTypes::DATETIME_MUTABLE)]
  28.     private ?\DateTimeInterface $endTime null;
  29.     #[ORM\Column]
  30.     private ?bool $accepted null;
  31.     #[ORM\ManyToOne(inversedBy'acceptedLeaveRequests')]
  32.     private ?Admin $acceptedBy null;
  33.     #[ORM\Column]
  34.     private ?bool $rejected null;
  35.     #[ORM\ManyToOne(inversedBy'rejectedLeaveRequests')]
  36.     private ?Admin $rejectedBy null;
  37.     #[ORM\ManyToOne(inversedBy'LeaveRequest')]
  38.     private ?LeaveRequestType $leaveRequestType null;
  39.     #[ORM\Column]
  40.     private ?bool $acceptedByHr false;
  41.     #[ORM\ManyToOne]
  42.     private ?Admin $acceptedByHrBy null;
  43.     #[ORM\Column(typeTypes::DATETIME_MUTABLEnullabletrue)]
  44.     private ?\DateTimeInterface $addedDate null;
  45.     #[ORM\Column]
  46.     private ?int $leaveCategory 1;
  47.     #[ORM\ManyToOne(inversedBy'leaveRequests')]
  48.     private ?Admin $admin null;
  49.     #[ORM\OneToMany(mappedBy'leaveRequest'targetEntityLeaveRequestAllocation::class, cascade: ['persist''remove'])]
  50.     private Collection $leaveRequestAllocations;
  51.     #[ORM\Column(length255nullabletrue)]
  52.     private ?string $workTimeSettlement null;
  53.     #[ORM\Column]
  54.     private ?bool $forHourlySettlement null;
  55.     #[ORM\Column(nullabletrue)]
  56.     private ?int $employmentFactor null;
  57.     public function __construct()
  58.     {
  59.         $this->consultant null;
  60.         $this->admin null;
  61.         $this->accepted false;
  62.         $this->rejected false;
  63.         $this->acceptedByHr false;
  64.         $this->addedDate = new DateTime();
  65.         $this->leaveRequestAllocations = new ArrayCollection();
  66.         $this->workTimeSettlement NULL;
  67.     }
  68.     public function getId(): ?int
  69.     {
  70.         return $this->id;
  71.     }
  72.     public function getConsultant(): ?Consultant
  73.     {
  74.         return $this->consultant;
  75.     }
  76.     public function setConsultant(?Consultant $consultant): static
  77.     {
  78.         $this->consultant $consultant;
  79.         return $this;
  80.     }
  81.     public function getStartTime(): ?\DateTimeInterface
  82.     {
  83.         return $this->startTime;
  84.     }
  85.     public function setStartTime(\DateTimeInterface $startTime): static
  86.     {
  87.         $this->startTime $startTime;
  88.         return $this;
  89.     }
  90.     public function getEndTime(): ?\DateTimeInterface
  91.     {
  92.         return $this->endTime;
  93.     }
  94.     public function setEndTime(\DateTimeInterface $endTime): static
  95.     {
  96.         $this->endTime $endTime;
  97.         return $this;
  98.     }
  99.     public function isAccepted(): ?bool
  100.     {
  101.         return $this->accepted;
  102.     }
  103.     public function setAccepted(bool $accepted): static
  104.     {
  105.         $this->accepted $accepted;
  106.         return $this;
  107.     }
  108.     public function getAcceptedBy(): ?Admin
  109.     {
  110.         return $this->acceptedBy;
  111.     }
  112.     public function setAcceptedBy(?Admin $acceptedBy): static
  113.     {
  114.         $this->acceptedBy $acceptedBy;
  115.         return $this;
  116.     }
  117.     public function isRejected(): ?bool
  118.     {
  119.         return $this->rejected;
  120.     }
  121.     public function setRejected(bool $rejected): static
  122.     {
  123.         $this->rejected $rejected;
  124.         return $this;
  125.     }
  126.     public function getRejectedBy(): ?Admin
  127.     {
  128.         return $this->rejectedBy;
  129.     }
  130.     public function setRejectedBy(?Admin $rejectedBy): static
  131.     {
  132.         $this->rejectedBy $rejectedBy;
  133.         return $this;
  134.     }
  135.     public function getLeaveRequestType(): ?LeaveRequestType
  136.     {
  137.         return $this->leaveRequestType;
  138.     }
  139.     public function setLeaveRequestType(LeaveRequestType $leaveRequestType): void
  140.     {
  141.         $this->leaveRequestType $leaveRequestType;
  142.     }
  143.     public function getDuration(): string
  144.     {
  145.         //method doesn't count public holidays, suitable for daily(leaveCategory == 2) leave requests
  146.         if (!$this->getStartTime() || !$this->getEndTime()) {
  147.             return 'N/A';
  148.         }
  149.         $category $this->getLeaveCategory();
  150.         $isChildCare $this->leaveRequestType $this->leaveRequestType->isChildCare() : false;
  151.         // Kategoria 1: Dzienne — zwracaj liczbę dni roboczych (bez weekendów), bez jednostki
  152.         if ($category === && !$isChildCare) {
  153.             $startTime = clone $this->getStartTime();
  154.             $endTime = clone $this->getEndTime();
  155.             // Ustaw tylko datę, aby liczyć pełne dni
  156.             $currentDay = (clone $startTime)->setTime(000);
  157.             $lastDay = (clone $endTime)->setTime(000);
  158.             // Jeśli zakres jest odwrócony
  159.             if ($lastDay $currentDay) {
  160.                 return '0';
  161.             }
  162.             $workingDays 0;
  163.             while ($currentDay <= $lastDay) {
  164.                 $dayOfWeek = (int)$currentDay->format('N'); // 1=pon, ..., 7=niedz
  165.                 if ($dayOfWeek !== && $dayOfWeek !== 7) {
  166.                     $workingDays++;
  167.                 }
  168.                 $currentDay = (clone $currentDay)->modify('+1 day');
  169.             }
  170.             return (string)$workingDays.' dni';
  171.         }
  172.         // Kategoria 2: Godzinowe — zwracaj różnicę czasu jako "Xh Ym"
  173.         if ($category === || $isChildCare) {
  174.             $start $this->getStartTime();
  175.             $end $this->getEndTime();
  176.             if ($end $start) {
  177.                 return '0 godz.';
  178.             }
  179.             $diffSeconds $end->getTimestamp() - $start->getTimestamp();
  180.             $totalMinutes = (int)floor($diffSeconds 60);
  181.             $hours intdiv($totalMinutes60);
  182.             $minutes $totalMinutes 60;
  183.             if ($hours && $minutes 0) {
  184.                 return sprintf('%d godz. %dm'$hours$minutes);
  185.             }
  186.             if ($hours 0) {
  187.                 return sprintf('%d godz.'$hours);
  188.             }
  189.             return sprintf('%d godz.'$minutes);
  190.         }
  191.         // Fallback: licz dni robocze jak w kategorii dziennej
  192.         $startTime = clone $this->getStartTime();
  193.         $endTime = clone $this->getEndTime();
  194.         $currentDay = (clone $startTime)->setTime(000);
  195.         $lastDay = (clone $endTime)->setTime(000);
  196.         if ($lastDay $currentDay) {
  197.             return '0';
  198.         }
  199.         $workingDays 0;
  200.         while ($currentDay <= $lastDay) {
  201.             $dayOfWeek = (int)$currentDay->format('N');
  202.             if ($dayOfWeek !== && $dayOfWeek !== 7) {
  203.                 $workingDays++;
  204.             }
  205.             $currentDay = (clone $currentDay)->modify('+1 day');
  206.         }
  207.         return (string)$workingDays.' dni';
  208.     }
  209.     public function getAcceptedOrRejected():string{
  210.         if($this->isRejected()){
  211.             return 'Odrzucone';
  212.         }
  213.         if($this->isAccepted()){
  214.             return 'Zaakceptowany';
  215.         }
  216.         return 'w trakcie';
  217.     }
  218.     public function isAcceptedByHr(): ?bool
  219.     {
  220.         return $this->acceptedByHr;
  221.     }
  222.     public function getAcceptedByHr(): bool
  223.     {
  224.         return $this->acceptedByHr;
  225.     }
  226.     public function setAcceptedByHr(bool $acceptedByHr): static
  227.     {
  228.         $this->acceptedByHr $acceptedByHr;
  229.         return $this;
  230.     }
  231.     public function getAcceptedByHrBy(): ?Admin
  232.     {
  233.         return $this->acceptedByHrBy;
  234.     }
  235.     public function setAcceptedByHrBy(?Admin $acceptedByHrBy): static
  236.     {
  237.         $this->acceptedByHrBy $acceptedByHrBy;
  238.         return $this;
  239.     }
  240.     public function getAddedDate(): ?\DateTimeInterface
  241.     {
  242.         return $this->addedDate;
  243.     }
  244.     public function setAddedDate(?\DateTimeInterface $addedDate): static
  245.     {
  246.         $this->addedDate $addedDate;
  247.         return $this;
  248.     }
  249.     public function getLeaveCategory(): ?int
  250.     {
  251.         return $this->leaveCategory;
  252.     }
  253.     public function setLeaveCategory(int $leaveCategory): static
  254.     {
  255.         $this->leaveCategory $leaveCategory;
  256.         return $this;
  257.     }
  258.     public function getAdmin(): ?Admin
  259.     {
  260.         return $this->admin;
  261.     }
  262.     public function setAdmin(?Admin $admin): static
  263.     {
  264.         $this->admin $admin;
  265.         return $this;
  266.     }
  267.     /**
  268.      * @return Collection<int, LeaveRequestAllocation>
  269.      */
  270.     public function getLeaveRequestAllocations(): Collection
  271.     {
  272.         return $this->leaveRequestAllocations;
  273.     }
  274.     public function getLeaveRequestPlannedMinutes(): int
  275.     {
  276.         return (int) (($this->endTime->getTimestamp() - $this->startTime->getTimestamp()) / 60);
  277.     }
  278.     public function getLeaveRequestUsedMinutes()
  279.     {
  280.         $sum 0;
  281.         foreach ($this->leaveRequestAllocations as $allocation) {
  282.             $sum += $allocation->getMinutesUsed();
  283.         }
  284.         return $sum;
  285.     }
  286.     public function getLeaveRequestRemainingMinutes(): int
  287.     {
  288.         return $this->getLeaveRequestPlannedMinutes() - $this->getLeaveRequestUsedMinutes();
  289.     }
  290.     public function addLeaveRequestAllocation(LeaveRequestAllocation $leaveRequestAllocation): static
  291.     {
  292.         if (!$this->leaveRequestAllocations->contains($leaveRequestAllocation)) {
  293.             $this->leaveRequestAllocations->add($leaveRequestAllocation);
  294.             $leaveRequestAllocation->setLeaveRequest($this);
  295.         }
  296.         return $this;
  297.     }
  298.     public function removeLeaveRequestAllocation(LeaveRequestAllocation $leaveRequestAllocation): static
  299.     {
  300.         if ($this->leaveRequestAllocations->removeElement($leaveRequestAllocation)) {
  301.             // set the owning side to null (unless already changed)
  302.             if ($leaveRequestAllocation->getLeaveRequest() === $this) {
  303.                 $leaveRequestAllocation->setLeaveRequest(null);
  304.             }
  305.         }
  306.         return $this;
  307.     }
  308.     public function getWorkTimeSettlement(): ?string
  309.     {
  310.         return $this->workTimeSettlement;
  311.     }
  312.     public function setWorkTimeSettlement(?string $workTimeSettlement): static
  313.     {
  314.         $this->workTimeSettlement $workTimeSettlement;
  315.         return $this;
  316.     }
  317.     #[Assert\Callback]
  318.     public function validateSingleLeaveRequestUser(ExecutionContextInterface $context): void
  319.     {
  320.         $hasAdmin $this->getAdmin() !== null;
  321.         $hasConsultant $this->getConsultant() !== null;
  322.         if ($hasAdmin !== $hasConsultant) {
  323.             return;
  324.         }
  325.         $message $hasAdmin
  326.             'Wybierz tylko jedno pole: Admin albo Consultant.'
  327.             'Wybierz Admina albo Consultanta.';
  328.         $context->buildViolation($message)
  329.             ->atPath('admin')
  330.             ->addViolation();
  331.         $context->buildViolation($message)
  332.             ->atPath('consultant')
  333.             ->addViolation();
  334.     }
  335.     #[Assert\Callback]
  336.     public function validateMinDailyDuration(ExecutionContextInterface $context): void
  337.     {
  338.         $start $this->getStartTime();
  339.         $end   $this->getEndTime();
  340.         $leaveRequestType $this->getLeaveRequestType();
  341.         $isChildCare $leaveRequestType->isChildCare();
  342.         if (!$start || !$end) {
  343.             return;
  344.         }
  345.         $user $this->getAdmin() ?? $this->getConsultant();
  346.         $nominalHours $this->resolveEmploymentFactorForValidation($user);
  347.         if (($end->getTimestamp() - $start->getTimestamp()) < ($nominalHours 3600) && !$isChildCare) {
  348.             $context->buildViolation('Wniosek urlopowy musi obejmować co najmniej {{ h }} godzin, aby pokryć nominalny czas pracy.')
  349.                 ->setParameter('{{ h }}', (string)$nominalHours)
  350.                 ->atPath('endTime')
  351.                 ->addViolation();
  352.         }
  353.     }
  354.     private function resolveEmploymentFactorForValidation(mixed $user): int
  355.     {
  356.         if ($user instanceof Admin) {
  357.             return $this->resolveEmploymentFactor(
  358.                 $user->isDisability(),
  359.                 $user->getAdminContractType(),
  360.                 $user->getNominalWorkTime()?->getEmploymentFactor()
  361.             );
  362.         }
  363.         if ($user instanceof Consultant) {
  364.             return $this->resolveEmploymentFactor(
  365.                 $user->isDisability(),
  366.                 $user->getConsultantContractType(),
  367.                 $user->getNominalWorkTime()?->getEmploymentFactor()
  368.             );
  369.         }
  370.         return 8;
  371.     }
  372.     private function resolveEmploymentFactor(bool $hasDisability, ?EmploymentContractType $contractType, ?int $nominalEmploymentFactor): int
  373.     {
  374.         if ($hasDisability && $this->isFullTimeContract($contractType)) {
  375.             return 8;
  376.         }
  377.         return $nominalEmploymentFactor ?? 8;
  378.     }
  379.     private function isFullTimeContract(?EmploymentContractType $contractType): bool
  380.     {
  381.         return $contractType !== null && abs(($contractType->getMultiplier() ?? 0.0) - 1.0) < 0.0001;
  382.     }
  383.     public function isForHourlySettlement(): ?bool
  384.     {
  385.         return $this->forHourlySettlement;
  386.     }
  387.     public function setForHourlySettlement(bool $forHourlySettlement): static
  388.     {
  389.         $this->forHourlySettlement $forHourlySettlement;
  390.         return $this;
  391.     }
  392.     public function getEmploymentFactor(): ?int
  393.     {
  394.         return $this->employmentFactor;
  395.     }
  396.     public function setEmploymentFactor(?int $employmentFactor): void
  397.     {
  398.         $this->employmentFactor $employmentFactor;
  399.     }
  400. }