<?php namespace App\Utils; use App\Entity\AccountAdherent; use App\Entity\AccountComptoir; use App\Entity\AccountGroupe; use App\Entity\AccountPrestataire; use App\Entity\AccountSiege; use App\Entity\AchatMonnaie; use App\Entity\Adherent; use App\Entity\Comptoir; use App\Entity\Flux; use App\Entity\Groupe; use App\Entity\OperationAdherent; use App\Entity\OperationComptoir; use App\Entity\OperationGroupe; use App\Entity\OperationPrestataire; use App\Entity\OperationSiege; use App\Entity\Prestataire; use App\Entity\Siege; use App\Entity\VenteEmlc; use App\Enum\CurrencyEnum; use App\Enum\MoyenEnum; use App\Events\FluxEvent; use App\Events\MLCEvents; use App\Flux\AccountInterface; use App\Flux\FluxInterface; use Doctrine\DBAL\LockMode; use Doctrine\ORM\EntityRepository; use FOS\UserBundle\Model\UserInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Security; class OperationUtils { private $em; private $session; private $security; private $eventDispatcher; public function __construct(CustomEntityManager $em, SessionInterface $session, Security $security, EventDispatcherInterface $eventDispatcher) { $this->em = $em; $this->session = $session; $this->security = $security; $this->eventDispatcher = $eventDispatcher; } private function getQueryOperations(EntityRepository $repo, AccountInterface $account, Request $request) { $qb = $repo ->createQueryBuilder('a') ->addSelect('flux') ->leftJoin('a.flux', 'flux') ->andWhere('a.account = :account') ->setParameter('account', $account->getId()) ->orderBy('a.createdAt', 'DESC') ; if (!empty($request->get('formListOperations')['datemin'])) { $qb ->andWhere('a.createdAt >= :datemin') ->setParameter('datemin', $request->get('formListOperations')['datemin']) // Date only comparison: error in queries comparing datetimes with some MYSQL versions ; } if (!empty($request->get('formListOperations')['datemax'])) { $qb ->andWhere('a.createdAt <= :datemax') ->setParameter('datemax', $request->get('formListOperations')['datemax']) ; } if (!empty($request->get('formListOperations')['moyen'])) { if (!in_array(($request->get('formListOperations')['moyen']), MoyenEnum::getAvailableTypes())) { throw new \InvalidArgumentException('Moyen de paiement invalide !'); } $qb ->andWhere('flux.moyen = :moyen') ->setParameter('moyen', $request->get('formListOperations')['moyen']) ; } if (!empty($request->get('formListOperations')['showMonAPurchaseOnly'])) { $qb ->andWhere('flux.type IN (:type_achat_monnaie_adherent, :type_vente_emlc_adherent)') ->setParameter('type_achat_monnaie_adherent', AchatMonnaie::TYPE_ACHAT_ADHERENT) ->setParameter('type_vente_emlc_adherent', VenteEmlc::TYPE_VENTE_EMLC_ADHERENT) ; } return $qb->getQuery(); } public function executeOperations(FluxInterface $flux) { try { $em = $this->em; $callback = function () use ($em, $flux) { $operations = $flux->getAllOperations($em); // 0/ Merge operations on same account (for cotisation+don and achatmonnaie+don) to check if balance is enough $operationsToCheck = []; foreach ($operations as $operation) { if (!empty($operationsToCheck) && isset($operationsToCheck[$operation->getAccount()->getId()->toString()])) { $op = $operationsToCheck[$operation->getAccount()->getId()->toString()]; $op->setMontant($op->getMontant() + $operation->getMontant()); $operationsToCheck[$operation->getAccount()->getId()->toString()] = $op; } else { $operationsToCheck[$operation->getAccount()->getId()->toString()] = $operation; } } // 1/ Check if all operations can be done without erros foreach ($operationsToCheck as $operation) { if (null == $operation->getAccount()) { throw new \Exception('No account !!'); } if ($operation->getAccount()->getCurrency() != $operation->getCurrency()) { throw new \Exception("Account's currency different from operation !"); } // Get repository inside callable to make sure EntityManager is valid $class = $em->getClassMetadata(get_class($operation->getAccount())); $accounts = $em->getRepository($class->getName()); // Fetch account with FOR UPDATE write lock $account = $accounts->find( $operation->getAccount()->getId(), LockMode::PESSIMISTIC_WRITE ); $newAccountBalance = $account->getBalance() + $operation->getMontant(); if ($newAccountBalance < 0) { throw new \Exception('Operation impossible, montant supérieur au solde !'); } } // 2/ If all is ok, persist operations and new account's balance ! foreach ($operations as $operation) { // Get repository inside callable to make sure EntityManager is valid $class = $em->getClassMetadata(get_class($operation->getAccount())); $accounts = $em->getRepository($class->getName()); // Fetch account with FOR UPDATE write lock $account = $accounts->find( $operation->getAccount()->getId(), LockMode::PESSIMISTIC_WRITE ); // $account->addOperation($operation); $account->addAmount($operation->getMontant()); $em->persist($operation); $em->persist($account); } $em->persist($flux); $em->flush(); }; $this->em->transactional($callback); $this->eventDispatcher->dispatch( MLCEvents::FLUX, new FluxEvent($flux) ); return true; } catch (\Exception $e) { // @TODO : tmp throw $e; } } public function getCurrentAccountable(UserInterface $user) { if (null != $this->session->get('_prestagere') && ($user->isGranted('ROLE_PRESTATAIRE') || $user->isGranted('ROLE_CAISSIER'))) { return $this->session->get('_prestagere'); } elseif (null != $user->getAdherent() && $user->isGranted('ROLE_ADHERENT')) { return $user->getAdherent(); } elseif (null != $this->session->get('_comptoirgere') && $user->isGranted('ROLE_COMPTOIR')) { return $this->session->get('_comptoirgere'); } elseif (null != $this->session->get('_groupegere') && ($user->isGranted('ROLE_GESTION_GROUPE') || $user->isGranted('ROLE_CONTACT'))) { return $this->session->get('_groupegere'); } elseif ($user->isGranted('ROLE_ADMIN_SIEGE')) { $siege = $this->em->getRepository(Siege::class)->getTheOne(); return $siege; } return null; } /** * @return Account[] Returns an array of Account objects */ public function getUserOperationsByCurrency(Request $request, string $currency = null) { if ($this->em->getFilters()->isEnabled('enabled_filter')) { $this->em->getFilters()->disable('enabled_filter'); } if (null == $this->security->getUser()) { return null; } $object = $this->getCurrentAccountable($this->security->getUser()); $repo = null; $account = null; if ($object instanceof Prestataire) { if (null != $currency) { $account = $this->em->getRepository(AccountPrestataire::class)->findOneBy(['prestataire' => $object, 'currency' => $currency]); } else { $account = $this->em->getRepository(AccountPrestataire::class)->findOneBy(['prestataire' => $object, 'currency' => CurrencyEnum::CURRENCY_EMLC]); } $repo = $this->em->getRepository(OperationPrestataire::class); } elseif ($object instanceof Adherent) { if (null != $currency) { $account = $this->em->getRepository(AccountAdherent::class)->findOneBy(['adherent' => $object, 'currency' => $currency]); } else { $account = $this->em->getRepository(AccountAdherent::class)->findOneBy(['adherent' => $object, 'currency' => CurrencyEnum::CURRENCY_EMLC]); } $repo = $this->em->getRepository(OperationAdherent::class); } elseif ($object instanceof Groupe) { $account = $this->em->getRepository(AccountGroupe::class)->findOneBy(['groupe' => $object, 'currency' => CurrencyEnum::CURRENCY_MLC]); $repo = $this->em->getRepository(OperationGroupe::class); } elseif ($object instanceof Comptoir) { $account = $this->em->getRepository(AccountComptoir::class)->findOneBy(['comptoir' => $object, 'currency' => CurrencyEnum::CURRENCY_MLC]); $repo = $this->em->getRepository(OperationComptoir::class); } elseif ($object instanceof Siege) { if (null != $currency) { if (!in_array($currency, CurrencyEnum::getAvailableTypes())) { throw new \Exception('Opération impossible ! Type de currency ' . $currency . ' inexistant'); } $account = $object->getAccountWithCurrency($currency); } else { $account = $this->em->getRepository(AccountSiege::class)->findOneBy(['siege' => $object, 'currency' => CurrencyEnum::CURRENCY_MLC]); } $repo = $this->em->getRepository(OperationSiege::class); } if (null == $repo || null == $account) { return null; } return $this->getQueryOperations($repo, $account, $request); } }