src/Admin/TransactionAdmin.php line 19

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Admin;
  4. use App\Entity\Reserve;
  5. use App\Entity\Transaction;
  6. use App\Entity\TransactionCategory;
  7. use App\Entity\Type\DirectionType;
  8. use Doctrine\ORM\QueryBuilder;
  9. use Sonata\AdminBundle\Datagrid\DatagridMapper;
  10. use Sonata\AdminBundle\Datagrid\ListMapper;
  11. use Sonata\AdminBundle\Form\FormMapper;
  12. use Sonata\AdminBundle\Form\Type\ModelType;
  13. use Sonata\AdminBundle\Show\ShowMapper;
  14. use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
  15. final class TransactionAdmin extends AbstractAdmin
  16. {
  17.     protected $datagridValues = [
  18.         '_sort_order' => 'DESC',
  19.         '_sort_by' => 'id'
  20.     ];
  21.     /**
  22.      * Super admin sees all transactions.
  23.      * Other admins see only their own, and never reserve-cancellation (OUT) rows —
  24.      * those are internal balance adjustments when an active rental is cancelled.
  25.      */
  26.     public function createQuery($context 'list')
  27.     {
  28.         /** @var QueryBuilder $query */
  29.         $query parent::createQuery($context);
  30.         if (!$this->isSuperAdmin()) {
  31.             $alias $query->getRootAliases()[0];
  32.             $query
  33.                 ->andWhere(sprintf('%s.admin = :currentAdmin'$alias))
  34.                 // Hide automatic "cancel active rental" reverse transactions
  35.                 ->andWhere(sprintf(
  36.                     'NOT (%s.direction = :reserveCancelDirection AND %s.targetEntity = :reserveEntity)',
  37.                     $alias,
  38.                     $alias
  39.                 ))
  40.                 ->setParameter('currentAdmin'$this->getCurrentUser())
  41.                 ->setParameter('reserveCancelDirection'DirectionType::OUT)
  42.                 ->setParameter('reserveEntity'Reserve::class);
  43.         }
  44.         return $query;
  45.     }
  46.     /**
  47.      * Prevent direct URL access to someone else's or hidden reverse-rental transactions.
  48.      *
  49.      * @param string            $action
  50.      * @param Transaction|null  $object
  51.      */
  52.     public function hasAccess($action$object null): bool
  53.     {
  54.         if (!parent::hasAccess($action$object)) {
  55.             return false;
  56.         }
  57.         if (
  58.             $object instanceof Transaction
  59.             && !$this->isSuperAdmin()
  60.             && in_array($action, ['edit''show''delete'], true)
  61.         ) {
  62.             if ($this->isReserveCancellationTransaction($object)) {
  63.                 return false;
  64.             }
  65.             return $this->isOwnedByCurrentUser($object);
  66.         }
  67.         return true;
  68.     }
  69.     protected function configureDatagridFilters(DatagridMapper $datagridMapper): void
  70.     {
  71.         $datagridMapper
  72.             ->add('id')
  73.             ->add('amount'null, ['label' => 'Сумма'])
  74.             ->add('direction'null, [
  75.                 'label' => 'Приход/Расход',
  76.             ], ChoiceType::class, [
  77.                 'choices' => array_flip(DirectionType::RU_VALUES),
  78.             ])
  79.             ->add('category'null, ['label' => 'Категория'])
  80.         ;
  81.         if ($this->isSuperAdmin()) {
  82.             $datagridMapper->add('admin'null, ['label' => 'Автор']);
  83.         }
  84.     }
  85.     protected function configureListFields(ListMapper $listMapper): void
  86.     {
  87.         $listMapper->add('amount'null, [
  88.             'template' => 'admin/Transaction/list_amount.html.twig',
  89.             'label' => 'Сумма'
  90.         ]);
  91.         $listMapper->add('direction'null, [
  92.             'template' => 'admin/Transaction/list_direction.html.twig',
  93.             'label' => 'Приход/Расход'
  94.         ]);
  95.         $listMapper->add('description'null, ['label' => 'Описание']);
  96.         $listMapper->add('category'null, ['label' => 'Категория']);
  97.         $listMapper->add('date'null, ['label' => 'Дата транзакции']);
  98.         $listMapper->add('createdAt'null, ['label' => 'Внесено в систему']);
  99.         if ($this->isSuperAdmin()) {
  100.             $listMapper->add('admin'null, [
  101.                 'template' => 'admin/Transaction/list_admin.html.twig',
  102.                 'label' => 'Автор'
  103.             ]);
  104.         }
  105.         $listMapper->add('_action'null, [
  106.             'actions' => [
  107.                 'show' => [],
  108.                 'edit' => [],
  109.             ],
  110.         ]);
  111.     }
  112.     protected function configureFormFields(FormMapper $formMapper): void
  113.     {
  114.         $formMapper->add('direction'ChoiceType::class, [
  115.             'choices' => array_flip(DirectionType::RU_VALUES),
  116.             'required' => true,
  117.             'label' => 'Приход/Расход',
  118.         ]);
  119.         $formMapper->add('amount'null, [
  120.             'label' => 'Сумма',
  121.             'help' => 'Сумма в BYN',
  122.         ]);
  123.         $formMapper->add('category'ModelType::class, [
  124.             'class' => TransactionCategory::class,
  125.             'required' => false,
  126.             'btn_add' => 'Добавить категорию',
  127.             'label' => 'Категория',
  128.         ]);
  129.         $formMapper->add('description'null, [
  130.             'label' => 'Описание',
  131.             'required' => false,
  132.         ]);
  133.         $formMapper->add('date'null, [
  134.             'label' => 'Дата транзакции',
  135.             'required' => false,
  136.             'help' => 'Можно задать любую дату для проводок задним числом. Если не указана — будет использована дата создания.',
  137.         ]);
  138.     }
  139.     protected function configureShowFields(ShowMapper $showMapper): void
  140.     {
  141.         $showMapper
  142.             ->add('id')
  143.             ->add('amount'null, ['label' => 'Сумма'])
  144.             ->add('direction''choice', [
  145.                 'label' => 'Приход/Расход',
  146.                 'choices' => DirectionType::RU_VALUES,
  147.             ])
  148.             ->add('category'null, ['label' => 'Категория'])
  149.             ->add('description'null, ['label' => 'Описание'])
  150.             ->add('date'null, ['label' => 'Дата транзакции'])
  151.             ->add('createdAt'null, ['label' => 'Внесено'])
  152.         ;
  153.         if ($this->isSuperAdmin()) {
  154.             $showMapper->add('admin'null, ['label' => 'Автор']);
  155.         }
  156.     }
  157.     /**
  158.      * @param Transaction $object
  159.      */
  160.     public function prePersist($object): void
  161.     {
  162.         $object->setAdmin($this->getCurrentUser());
  163.         // Если дата транзакции не указана вручную — используем текущий момент
  164.         if ($object->getDate() === null) {
  165.             $object->setDate($object->getCreatedAt() ?? new \DateTime());
  166.         }
  167.     }
  168.     public function preUpdate($object): void
  169.     {
  170.         // При редактировании тоже страхуемся
  171.         if ($object->getDate() === null) {
  172.             $object->setDate($object->getCreatedAt() ?? new \DateTime());
  173.         }
  174.     }
  175.     private function isSuperAdmin(): bool
  176.     {
  177.         $authorizationChecker $this->getContainer()->get('security.authorization_checker');
  178.         return $authorizationChecker->isGranted('ROLE_SUPER_ADMIN');
  179.     }
  180.     private function isOwnedByCurrentUser(Transaction $transaction): bool
  181.     {
  182.         $author $transaction->getAdmin();
  183.         if ($author === null) {
  184.             return false;
  185.         }
  186.         return $author->getId() === $this->getCurrentUser()->getId();
  187.     }
  188.     /**
  189.      * OUT transactions linked to Reserve = reverse on cancel of active rental.
  190.      */
  191.     private function isReserveCancellationTransaction(Transaction $transaction): bool
  192.     {
  193.         return (int) $transaction->getDirection() === DirectionType::OUT
  194.             && $transaction->getTargetEntity() === Reserve::class;
  195.     }
  196. }