<?php
declare(strict_types=1);
namespace App\Nolimit\AnualLeave\Admin;
use App\Entity\AnualLeave;
use App\Entity\AnualLeaveType;
use App\Repository\AnualLeaveRepository;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\DoctrineORMAdminBundle\Filter\CallbackFilter;
use Sonata\Form\Type\DatePickerType;
use Sonata\Form\Type\DateRangePickerType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Security\Core\Security;
use Sonata\AdminBundle\Route\RouteCollectionInterface;
final class AnualLeaveAdmin extends AbstractAdmin
{
public function __construct(
private readonly Security $security,
private readonly AnualLeaveRepository $anualLeaveRepository
) {
parent::__construct();
}
protected function configureRoutes(RouteCollectionInterface $collection): void
{
$collection
->remove('delete')
->add('delete_log', $this->getRouterIdParameter().'/delete_log');
}
protected function configureDatagridFilters(DatagridMapper $filter): void
{
$filter
->add('userOnHoliday')
->add('name')
->add('description')
->add('dateInterval', CallbackFilter::class, [
'label' => 'Dátum intervallum',
'show_filter' => true,
'field_type' => DateRangePickerType::class,
'field_options' => [
'field_options' => [
'format' => 'yyyy-MM-dd',
],
],
'callback' => function ($query, $alias, $field, $data) {
$value = $data->getValue();
if (!\is_array($value) || !isset($value['start'], $value['end'])) {
return false;
}
$start = $value['start'];
$end = $value['end'];
if (!$start instanceof \DateTimeInterface || !$end instanceof \DateTimeInterface) {
return false;
}
$qb = $query->getQueryBuilder();
$rootAlias = current($query->getRootAliases());
$suffix = substr(uniqid('', true), -8);
$endParam = 'dt_end_' . $suffix;
$startParam = 'dt_start_' . $suffix;
$qb->andWhere($rootAlias . '.startDate <= :' . $endParam);
$qb->andWhere($rootAlias . '.endDate >= :' . $startParam);
$qb->setParameter($endParam, $end, \Doctrine\DBAL\Types\Types::DATE_MUTABLE);
$qb->setParameter($startParam, $start, \Doctrine\DBAL\Types\Types::DATE_MUTABLE);
return true;
},
])
->add('accepted')
->add('anualLeaveType', null, [
'show_filter' => true,
'label' => 'Szabadság típusa',
])
;
}
protected function configureDefaultFilterValues(array &$filterValues): void
{
$filterValues['dateInterval'] = [
'type' => null,
'value' => [
'start' => (new \DateTime('first day of January ' . date('Y')))->format('Y-m-d'),
'end' => (new \DateTime('last day of December ' . date('Y')))->format('Y-m-d'),
],
];
$filterValues['anualLeaveType'] = [
'type' => null,
'value' => '1',
];
}
protected function configureQuery($query): \Sonata\AdminBundle\Datagrid\ProxyQueryInterface
{
$query = parent::configureQuery($query);
$rootAlias = current($query->getRootAliases());
$query->andWhere($query->expr()->eq($rootAlias . '.deleted', ':deleted'));
$query->setParameter('deleted', false);
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
$query->andWhere(
$query->expr()->eq($rootAlias . '.userOnHoliday', ':userId')
);
$query->setParameter('userId', $this->security->getUser()->getId());
}
$filters = $this->getRequest()->query->get('filter', []);
$dateIntervalValue = $filters['dateInterval']['value'] ?? null;
$hasDateInterval = \is_array($dateIntervalValue)
&& !empty($dateIntervalValue['start'])
&& !empty($dateIntervalValue['end']);
if (!$hasDateInterval) {
$query->andWhere($query->expr()->between(
$rootAlias . '.startDate',
':start_date',
':end_date'
));
$query->setParameter('start_date', new \DateTime('first day of January ' . date('Y')));
$query->setParameter('end_date', new \DateTime('last day of December ' . date('Y')));
}
return $query;
}
protected function configureListFields(ListMapper $list): void
{
$list
->addIdentifier('name')
->add('userOnHoliday')
->add('accepted')
->add('description')
->add('startDate','date', array('sortable' => true))
->add('endDate', 'date')
->add('anualLeaveType')
->add(ListMapper::NAME_ACTIONS, null, [
'actions' => [
'show' => [],
'edit' => [],
'delete_log' => [
'template' => '/CRUD/logical_delete_with_date_condition.html.twig',
]
],
]);
}
protected function configureFormFields(FormMapper $form): void
{
$disabledInput = false;
$isOfficialHoliday = false;
if ($this->isCurrentRoute('edit')) {
$subject = $this->getSubject();
$startDate = $subject->getStartDate();
$isOfficialHoliday = $this->isOfficialHolidayLeave($subject);
if ($startDate < new \DateTime('now') && !$this->isGranted('ROLE_SUPER_ADMIN')) {
$disabledInput = true;
}
$user = $subject->getUserOnHoliday();
} else {
$user = $this->security->getUser();
}
$isEditing = $this->isCurrentRoute('edit');
$form
->add('userOnHoliday', EntityType::class, [
'class' => 'App\Entity\User',
'label' => 'Felhasználó',
'choice_label' => 'username',
'query_builder' => function ($repository) {
return $repository->createQueryBuilder('u')
->orderBy('u.username', 'ASC');
},
'data' => $user,
'disabled' => !$this->isGranted('ROLE_SUPER_ADMIN'),
])
->add('accepted', null, ['disabled' => !$this->isGranted('ROLE_SUPER_ADMIN')])
->add('name')
->add('description')
->add('startDate', DatePickerType::class, [
'label' => 'Start Date',
'format' => 'yyyy-MM-dd',
'required' => true,
])
;
// Hivatalos szabadnap: endDate = startDate (preUpdate biztosítja mentéskor)
if ($isOfficialHoliday) {
$form->add('endDate', DatePickerType::class, [
'label' => 'End Date',
'required' => true,
'format' => 'yyyy-MM-dd',
'attr' => ['readonly' => 'readonly'],
]);
} else {
$form->add('endDate', DatePickerType::class, [
'label' => 'End Date',
'required' => true,
'format' => 'yyyy-MM-dd',
]);
}
// Típus mező:
// - Szerkesztésnél: DISABLED, de MINDEN típus látszik (query_builder = null)
// - Hozzáadásnál + nem super admin: csak pihenő szabadság (type 1)
// - Hozzáadásnál + super admin: minden típus
$isTypeDisabled = $isEditing;
$typeQueryBuilder = null;
if (!$isEditing && !$this->isGranted('ROLE_SUPER_ADMIN')) {
$typeQueryBuilder = fn ($repo) => $repo->createQueryBuilder('t')
->andWhere('t.id = 1')
->orderBy('t.name', 'ASC');
}
$form->add('anualLeaveType', EntityType::class, [
'disabled' => $isTypeDisabled,
'class' => AnualLeaveType::class,
'choice_label' => 'name',
'label' => 'Tipus',
'query_builder' => $typeQueryBuilder,
'attr' => [
'class' => 'select2',
],
]);
// Hivatalos szabadnapnál fixed nem szerkeszthető (generálásból jön, isWeekendWorkDay alapján)
$form->add('fixed', null, [
'label' => 'LeaveFixed',
'disabled' => $isOfficialHoliday,
]);
}
private function isOfficialHolidayLeave($subject): bool
{
$type = $subject->getAnualLeaveType();
return $type && (int) $type->getId() === 2;
}
protected function configureShowFields(ShowMapper $show): void
{
$show
->add('id')
->add('name')
->add('description')
->add('startDate')
->add('endDate')
;
}
/**
* preValidate: validáció ELŐTT fut le.
* - Hivatalos nap: endDate = startDate szinkronizálás + duplikátum ellenőrzés
* - Nem super admin: dátumok nem lehetnek az aktuális évnél régebbiek
*/
public function preValidate(object $object): void
{
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
$firstDayOfYear = new \DateTimeImmutable('first day of January ' . date('Y'));
$startDate = $object->getStartDate();
$endDate = $object->getEndDate();
if ($startDate && $startDate < $firstDayOfYear) {
$this->getForm()->addError(new \Symfony\Component\Form\FormError(
'A kezdő dátum nem lehet az aktuális évnél régebbi.'
));
}
if ($endDate && $endDate < $firstDayOfYear) {
$this->getForm()->addError(new \Symfony\Component\Form\FormError(
'A befejezés dátuma nem lehet az aktuális évnél régebbi.'
));
}
}
if ($this->isOfficialHolidayLeave($object)) {
$object->setEndDate($object->getStartDate());
$excludeId = $object->getId();
if ($this->hasDuplicateOfficialHoliday($object, $excludeId)) {
$this->getForm()->addError(new \Symfony\Component\Form\FormError(
'Ehhez a felhasználóhoz már létezik hivatalos szabadnap erre a dátumra.'
));
}
}
}
public function prePersist($object): void
{
parent::prePersist($object);
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
$object->setUserOnHoliday($this->security->getUser());
$this->ensureVacationTypeOnly($object);
}
if ($this->isOfficialHolidayLeave($object)) {
$object->setEndDate($object->getStartDate());
}
}
public function preUpdate($object): void
{
parent::preUpdate($object);
if ($this->isOfficialHolidayLeave($object)) {
$object->setEndDate($object->getStartDate());
}
}
/**
* @return bool true ha duplikátum van
*/
private function hasDuplicateOfficialHoliday(AnualLeave $object, ?int $excludeId): bool
{
$user = $object->getUserOnHoliday();
if (!$user) {
return false;
}
return $this->anualLeaveRepository->hasOfficialHolidayOverlap(
$user->getId(),
$object->getStartDate(),
$object->getEndDate(),
$excludeId
);
}
private function ensureVacationTypeOnly($object): void
{
$type = $object->getAnualLeaveType();
if ($type && (int) $type->getId() !== 1) {
$vacationType = $this->getModelManager()->find(AnualLeaveType::class, 1);
if ($vacationType) {
$object->setAnualLeaveType($vacationType);
}
}
}
}