<?php namespace App\Controller; use AndrewSvirin\Ebics\Builders\CustomerCreditTransfer\CustomerCreditTransferBuilder; use App\Entity\Adherent; use App\Entity\Don; use App\Entity\Flux; use App\Entity\GlobalParameter; use App\Entity\Groupe; use App\Entity\Prestataire; use App\Entity\Reconversion; use App\Entity\User; use App\Enum\CurrencyEnum; use App\Flux\FluxInterface; use App\Utils\CustomEntityManager; use App\Utils\OperationUtils; use App\Utils\TAVCotisationUtils; use FOS\UserBundle\Model\UserManagerInterface; use FOS\UserBundle\Util\TokenGeneratorInterface; use Gamez\Symfony\Component\Serializer\Normalizer\UuidNormalizer; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; use Sonata\Exporter\Handler; use Sonata\Exporter\Source\DoctrineORMQuerySourceIterator; use Sonata\Exporter\Writer\CsvWriter; use Sonata\Exporter\Writer\JsonWriter; use Sonata\Exporter\Writer\XlsWriter; use Sonata\Exporter\Writer\XmlWriter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Form\Form; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\TranslatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Twig\Environment; /** * Types de transfert : (Les transferts dans la structure sont les flux de billets détenus par les opérateurs.). * * - SIEGE => GROUPES LOCAUX (Transfert du siège au groupe) * - GROUPE => SIEGE (Transfert du groupe au siège) * - GROUPES LOCAUX => COMPTOIRS (Transfert du groupe au comptoir) * - COMPTOIRS => GROUPES LOCAUX (Transfert du comptoir au groupe) * - COMPTOIRS => ADHERENTS (Diffusion de monnaie papier auprès des adhérents) * - COMPTOIRS => PRESTATAIRES (Diffusion de monnaie papier auprès des prestataires) * - PRESTATAIRES => COMPTOIRS (Reconversion) * * Types de transaction : * * - PRESTATAIRES => ADHERENTS (Virement vers un adherent) * - PRESTATAIRES => PRESTATAIRES (Virement entre prestataires) * - ADHERENTS => PRESTATAIRES (Paiement numérique) * - SIEGE => ADHERENTS (Achat de monnaie numérique par CB d'un adhérent) * - SIEGE => PRESTATAIRES (Achat de monnaie numérique par CB d'un prestataire) * - COMPTOIR => ADHERENTS (Vente de monnaie numérique à un adhérent) * - COMPTOIR => PRESTATAIRES (Vente de monnaie numérique à un prestataire) * Changes : * - COMPTOIR => ADHERENTS (Retrait de monnaie numérique en échange de billets MLC à un adhérent) * - COMPTOIR => PRESTATAIRES (Retrait de monnaie numérique en échange de billets MLC à un prestataire) */ class FluxController extends AbstractController { protected $em; protected $security; protected $translator; protected $eventDispatcher; protected $session; protected $templating; protected $operationUtils; protected $tokenGenerator; protected $validator; protected $userManager; protected $router; public function __construct( Security $security, CustomEntityManager $em, TranslatorInterface $translator, EventDispatcherInterface $eventDispatcher, SessionInterface $session, Environment $templating, OperationUtils $operationUtils, TAVCotisationUtils $tavCotisationUtils, TokenGeneratorInterface $tokenGenerator, ValidatorInterface $validator, CsrfTokenManagerInterface $tokenManager, UserManagerInterface $userManager, RouterInterface $router ) { $this->security = $security; $this->em = $em; $this->translator = $translator; $this->eventDispatcher = $eventDispatcher; $this->session = $session; $this->templating = $templating; $this->operationUtils = $operationUtils; $this->tokenGenerator = $tokenGenerator; $this->validator = $validator; $this->tokenManager = $tokenManager; $this->tavCotisationUtils = $tavCotisationUtils; $this->userManager = $userManager; $this->router = $router; } protected function manageFluxForm(Request $request, Form $form, $template = '@kohinos/flux/transaction.html.twig', $params = []) { // @TODO : better error management !! => show error on form instead of exception ! if (null == $this->security->getUser()) { throw new \Exception('[FLUX] Opération impossible ! Utilisateur déconnecté !'); // return new Response('Opération impossible ! Vous êtes déconnecté !', Response::HTTP_BAD_REQUEST); } // if (!$request->isXmlHttpRequest()) { // return new Response('', Response::HTTP_BAD_REQUEST); // } $form->handleRequest($request); if ($form->isSubmitted() && !$this->tokenManager->isTokenValid($this->tokenManager->getToken('flux_form'))) { $referer = $request->headers->get('referer'); return $this->redirect($referer); } if ($form->isSubmitted() && $form->isValid()) { $this->tokenManager->refreshToken('flux_form'); $flux = $form->getData(); try { if ($this->operationUtils->executeOperations($flux)) { // Redirect to confirmation page return $this->fluxSuccessRedirection($request, $flux); } } catch (\Exception $e) { $this->addFlash( 'error', $this->translator->trans($flux->getType() . '_title', [], 'flux') . ' : ' . $e->getMessage() ); $referer = $request->headers->get('referer'); if ($referer) { return $this->redirect($referer); } } } return $this->render($template, array_merge([ 'form' => $form->createView(), ], $params)); } private function fluxSuccessRedirection(Request $request, FluxInterface $flux) { $referer = $request->headers->get('referer'); $template = null; if ($this->templating->getLoader()->exists('@kohinos/flux/confirmationPage/' . $flux->getParentType() . '.html.twig')) { $template = '@kohinos/flux/confirmationPage/' . $flux->getParentType() . '.html.twig'; if ($this->templating->getLoader()->exists('@kohinos/flux/confirmationPage/' . $flux->getParentType() . '-' . $flux->getType() . '.html.twig')) { $template = '@kohinos/flux/confirmationPage/' . $flux->getParentType() . '-' . $flux->getType() . '.html.twig'; } } // Redirect to previous page in no confirmation page exist if (null === $template && $referer) { $this->addFlash( 'success', $this->translator->trans($flux->getType() . '_title', [], 'flux') . ' : ' . $this->translator->trans('operation_success', [], 'flux') ); return $this->redirect($referer); } return $this->render($template, [ 'flux' => $flux, ]); } /** * @param Request $request * @Route("/credit-transfer-file", name="credit_transfer_file") * @IsGranted({"ROLE_TRESORIER", "ROLE_SUPER_ADMIN"}) * @return Response */ public function creditTransferFileAction(Request $request) { //raison, bic and iban from debitor are fetched in global parameters $globalParametersRepository = $this->em->getRepository(GlobalParameter::class); $raison = $globalParametersRepository->val(GlobalParameter::VIREMENT_RECONVERSION_RAISON_GESTIONNAIRE); $bic = $globalParametersRepository->val(GlobalParameter::VIREMENT_RECONVERSION_BIC_GESTIONNAIRE); $iban = $globalParametersRepository->val(GlobalParameter::VIREMENT_RECONVERSION_IBAN_GESTIONNAIRE); //make sure raison, bic and iban are not empty if(!$raison || !$bic || !$iban) { $this->addFlash( 'sonata_flash_error', "Opération annulée car la raison, l'IBAN ou le BIC du gestionnaire pour les virements de reconversions est vide." ); return $this->redirect($this->router->generate('index') . 'admin/app/reconversion/list'); } //SEPA $builder = new CustomerCreditTransferBuilder(); $customerCreditTransfer = $builder ->createInstance( $bic, $iban, $raison ); $reconversions = $this->em->getRepository(Reconversion::class)->findBy(["reconverti" => false, "reconversionCcas" => false]); if(!$reconversions) { $this->addFlash( 'sonata_flash_error', "Aucune reconversion à traiter." ); return $this->redirect($this->router->generate('index') . 'admin/app/reconversion/list'); } foreach($reconversions as $r) { /* @var Reconversion $r */ /* @var Prestataire $presta */ $presta = $r->getExpediteur(); if(!$presta || !$presta->isEnabled()) { //fail ; do not flush : we don't want to toggle reconverti when file can't be generated $this->addFlash( 'sonata_flash_error', "Opération annulée car le prestataire " . $presta->getRaison() . " est désactivé ou introuvable." ); return $this->redirect($this->router->generate('index') . 'admin/app/reconversion/list'); } if(!$presta->getIban() || !$presta->getBic()) { //fail ; do not flush : we don't want to toggle reconverti when file can't be generated $this->addFlash( 'sonata_flash_error', "Opération annulée car l'IBAN ou le BIC du prestataire " . $presta->getRaison() . " est vide." ); return $this->redirect($this->router->generate('index') . 'admin/app/reconversion/list'); } $customerCreditTransfer ->addTransaction( $presta->getBic(), $presta->getIban(), $presta->getRaison(), $r->getMontant(), 'EUR', 'Reconversion MonA vers EUR' ); $r->setReconverti(true); } $filename = sprintf( 'credit_transfer_file_%s.%s', date('Y_m_d_H_i_s', strtotime('now')), 'xml' ); //projectDir is composer.json $dir = $this->getParameter('kernel.project_dir') . "/reconversions"; if (!is_dir($dir)) { mkdir($dir, 0755, true); } $path = $dir . "/" . $filename; $customerCreditTransfer->popInstance()->save($path); $this->em->flush(); return $this->file($path); } /** * Export all operations for a user role. * * @param Request $request Request * @param string $format Format of export ('json', 'xml', 'csv', 'xls') * @Route("/operation/export/{format}/{currency}", name="exportUserOperation", defaults={"format": "csv", "currency" : "emlc"}) */ public function exportUserOperationAction(Request $request, $format = 'csv', string $currency = CurrencyEnum::CURRENCY_EMLC) { $query = $this->operationUtils->getUserOperationsByCurrency($request, $currency); if (null != $query) { // Prepare the data source $fields = [ 'Date' => 'createdAt', 'Expediteur' => 'flux.expediteur', 'Destinataire' => 'flux.destinataire', 'Montant' => 'montant', 'Type parent' => 'flux.parenttype', 'Type' => 'flux.type', 'Moyen' => 'flux.moyen', 'Référence' => 'flux.reference', ]; $source = new DoctrineORMQuerySourceIterator($query, $fields); $filename = sprintf( 'export_operations_%s.%s', date('Y_m_d_H_i_s', strtotime('now')), $format ); foreach ($source as $data) { $data['Date'] = (new \Datetime($data['Date']))->format('d/m/Y H:i:s'); $data['Type'] = $this->translator->trans($data['Type'], [], 'flux'); $data['Montant'] = number_format($data['Montant'], 2, '.', ''); } return $this->getResponse( $format, $filename, $source ); } else { $this->addFlash( 'error', $this->translator->trans('Export des opérations impossible.') ); return $this->redirectToRoute('index'); } } /** * Export all transferts / transactions for a user role. * * @param Request $request Request * @param string $format Format of export ('json', 'xml', 'csv', 'xls') * @Route("/flux/export/{format}/", name="exportUserFlux", defaults={"format": "csv"}) */ public function exportFluxAction(Request $request, $format = 'csv') { //Prepare query depending on user role $query = null; $user = $this->security->getUser(); if (null != $this->session->get('_prestagere') && $user->isGranted('ROLE_PRESTATAIRE')) { $query = $this->em->getRepository(Flux::class)->getQueryByPrestataire($this->session->get('_prestagere')); } elseif (null != $user->getAdherent() && $user->isGranted('ROLE_ADHERENT')) { $query = $this->em->getRepository(Flux::class)->getQueryByAdherent($user->getAdherent()); } elseif (null != $this->session->get('_comptoirgere') && $user->isGranted('ROLE_COMPTOIR')) { $query = $this->em->getRepository(Flux::class)->getQueryByComptoir($this->session->get('_comptoirgere')); } elseif (null != $this->session->get('_groupegere') && $user->isGranted('ROLE_GESTION_GROUPE')) { $query = $this->em->getRepository(Flux::class)->getQueryByGroupe($this->session->get('_groupegere')); } if (null != $query) { // Prepare the data source $fields = [ 'Date' => 'createdAt', 'Expediteur' => 'expediteur', 'Destinataire' => 'destinataire', 'Type' => 'type', 'Montant' => 'montant', 'Moyen' => 'moyen', 'Opérateur' => 'operateur', 'Référence' => 'reference', ]; $source = new DoctrineORMQuerySourceIterator($query, $fields); $filename = sprintf( 'export_flux_%s.%s', date('Y_m_d_H_i_s', strtotime('now')), $format ); foreach ($source as $data) { $data['Date'] = (new \Datetime($data['Date']))->format('d/m/Y H:i:s'); $data['Type'] = $this->translator->trans($data['Type'], [], 'flux'); $data['Montant'] = number_format($data['Montant'], 2, '.', ''); } return $this->getResponse( $format, $filename, $source ); } else { $this->addFlash( 'error', $this->translator->trans('Export impossible.') ); return $this->redirectToRoute('index'); } } private function getResponse($format, $filename, $source) { switch ($format) { case 'xls': $writer = new XlsWriter('php://output'); $contentType = 'application/vnd.ms-excel'; break; case 'xml': $writer = new XmlWriter('php://output'); $contentType = 'text/xml'; break; case 'json': $writer = new JsonWriter('php://output'); $contentType = 'application/json'; break; case 'csv': $writer = new CsvWriter('php://output', ',', '"', '\\', true, true); $contentType = 'text/csv'; break; default: throw new \RuntimeException('Invalid format'); } $callback = static function () use ($source, $writer) { $handler = Handler::create($source, $writer); $handler->export(); }; return new StreamedResponse($callback, 200, [ 'Content-Type' => $contentType, 'Content-Disposition' => sprintf('attachment; filename="%s"', $filename), ]); } }