<?php
namespace App\Controller;
use App\Entity\Solicitacao;
use App\Form\SolicitacaoType;
use App\Entity\User;
use App\Repository\SolicitacaoRepository;
use App\Utils\FormSolicitacaoUtil;
use App\Utils\VerificaRole;
use App\Service\FileUploader;
use App\Service\Watermark;
use App\Service\Mailer;
use App\Service\SpreadsheetGenerator;
use Knp\Component\Pager\PaginatorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
#[Route('/solicitacao')]
class SolicitacaoController extends AbstractController
{
#[Route('/pendentes', name: 'app_solicitacao_pendentes', methods: ['GET'])]
public function solicitacaoPendentes(
SolicitacaoRepository $solicitacaoRepository,
Request $request,
PaginatorInterface $paginator
): Response {
$qb = $solicitacaoRepository->qbSolicitacaoByUserStatus($this->getUser(), Solicitacao::STATUS_PENDENTE);
$pagination = $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
return $this->render('solicitacao/index.html.twig', [
'solicitacoes' => $pagination,
'nav_active' => 'Solicitação',
]);
}
#[Route('/pre-aprovados', name: 'app_solicitacao_pre_aprovados', methods: ['GET'])]
public function solicitacaoPreAprovadas(
SolicitacaoRepository $solicitacaoRepository,
Request $request,
PaginatorInterface $paginator
): Response {
$qb = $solicitacaoRepository->qbSolicitacaoByUserStatus($this->getUser(), Solicitacao::STATUS_APROVADOR_OK);
$pagination = $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
return $this->render('solicitacao/index.html.twig', [
'solicitacoes' => $pagination,
'nav_active' => 'Solicitação',
]);
}
#[Route('/aprovados', name: 'app_solicitacao_aprovados', methods: ['GET'])]
public function solicitacaoAprovadas(
SolicitacaoRepository $solicitacaoRepository,
Request $request,
PaginatorInterface $paginator
): Response {
$qb = $solicitacaoRepository->qbSolicitacaoByUserStatus($this->getUser(), Solicitacao::STATUS_ADMINISTRADOR_OK);
$pagination = $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
return $this->render('solicitacao/index.html.twig', [
'solicitacoes' => $pagination,
'nav_active' => 'Solicitação',
]);
}
#[Route('/todos', name: 'app_solicitacao_todos', methods: ['GET'])]
public function solicitacaoTodas(
SolicitacaoRepository $solicitacaoRepository,
Request $request,
PaginatorInterface $paginator
): Response {
$qb = $solicitacaoRepository->qbSolicitacaoByUser($this->getUser());
$pagination = $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
return $this->render('solicitacao/index.html.twig', [
'solicitacoes' => $pagination,
'nav_active' => 'Solicitação',
]);
}
#[Route('/recusados', name: 'app_solicitacao_recusados', methods: ['GET'])]
public function solicitacaoRecusados(
SolicitacaoRepository $solicitacaoRepository,
Request $request,
PaginatorInterface $paginator
): Response {
$qb = $solicitacaoRepository->qbRecusados($this->getUser());
$pagination = $paginator->paginate($qb, $request->query->getInt('page', 1), 10);
return $this->render('solicitacao/index.html.twig', [
'solicitacoes' => $pagination,
'nav_active' => 'Solicitação',
]);
}
#[Route('/new', name: 'app_solicitacao_new', methods: ['GET', 'POST'])]
public function new(
Request $request,
FormSolicitacaoUtil $formSolicitacaoUtil,
VerificaRole $verificiaRole,
SolicitacaoRepository $solicitacaoRepository,
FileUploader $fileUploader,
Watermark $watermark,
Mailer $mailer
): Response {
$solicitacao = new Solicitacao();
$form = $this->createForm(SolicitacaoType::class, $solicitacao);
$form->handleRequest($request);
/** @var User $user */
$user = $this->getUser();
$isSuperUser = $this->isGranted('ROLE_SUPER');
$empresasUsuario = $user->getEmpresas();
if (!$empresasUsuario) {
throw new \Exception('Usuário sem empresa vinculada.');
}
if ($form->isSubmitted() && $form->isValid()) {
$solicitacao = $formSolicitacaoUtil->formatter($solicitacao);
if ($form->getClickedButton() && 'salvar' === $form->getClickedButton()->getName()) {
$solicitacaoRepository->add($solicitacao, true, $this->getUser());
if ($isSuperUser) {
$watermark->addWatermark($solicitacao, $this->getParameter('kernel.project_dir'), $this->getUser());
}
$mailer->sendMail($solicitacao);
$this->addFlash('success', 'Solicitação adicionada com sucesso.');
return $this->redirectToRoute('app_solicitacao_pendentes', [], Response::HTTP_SEE_OTHER);
}
if ($form->getClickedButton() && 'recorrente' === $form->getClickedButton()->getName()) {
$solicitacaoRepository->add($solicitacao, true, $this->getUser());
if ($isSuperUser) {
$watermark->addWatermark($solicitacao, $this->getParameter('kernel.project_dir'), $this->getUser());
} else {
$solicitacao->setAprovador($this->getUser());
$mailer->sendMail($solicitacao);
}
$this->addFlash('success', 'Solicitação adicionada com sucesso.');
}
}
return $this->renderForm('solicitacao/new.html.twig', [
'solicitacao' => $solicitacao,
'form' => $form,
'nav_active' => 'Solicitação',
]);
}
#[Route('/checker', name: 'app_solicitacao_recorrente', methods: ['POST'])]
public function checker(Request $request, SolicitacaoRepository $solicitacaoRepository): Response
{
$post_data = json_decode($request->getContent(), true) ?? [];
$numeroLancamento = $post_data['numeroLancamento'] ?? null;
if ($numeroLancamento === null) {
return $this->json(false);
}
$isChecked = $solicitacaoRepository->findBy(['numeroLancamento' => $numeroLancamento]);
return $this->json(count($isChecked) > 0);
}
#[Route('/empresa/{empresaId}/subempresa/{subEmpresaId}/solicitacao/{id}', name: 'app_solicitacao_show', methods: ['GET'])]
public function show(
SolicitacaoRepository $solicitacaoRepository,
Solicitacao $solicitacao,
int $empresaId,
int $subEmpresaId
): Response {
/** @var User $user */
$user = $this->getUser();
$hasAccess = false;
if ($this->isGranted('ROLE_SUPER')) {
$hasAccess = true;
} else {
foreach ($user->getSubEmpresas() as $subEmpresa) {
if ($subEmpresa->getId() === $subEmpresaId) {
$hasAccess = true;
break;
}
}
}
if ($solicitacao->getUsuario() === $user) {
$hasAccess = true;
}
if (!$hasAccess) {
throw $this->createAccessDeniedException('Você não tem permissão para acessar as solicitações desta empresa.');
}
$duplicidadeList = $solicitacaoRepository->findBy([
'numeroLancamento' => $solicitacao->getNumeroLancamento(),
]);
$duplicidade = count($duplicidadeList) > 1;
return $this->render('solicitacao/show.html.twig', [
'solicitacao' => $solicitacao,
'nav_active' => 'Solicitação',
'duplicidade' => $duplicidade,
]);
}
#[Route('/{id}/edit', name: 'app_solicitacao_edit', methods: ['GET', 'POST'])]
public function edit(
Request $request,
Solicitacao $solicitacao,
SolicitacaoRepository $solicitacaoRepository
): Response {
if (
$solicitacao->getStatus() !== Solicitacao::STATUS_PENDENTE
|| $solicitacao->getUsuario() !== $this->getUser()
) {
return $this->redirectToRoute('app_solicitacao_redirect');
}
$form = $this->createForm(SolicitacaoType::class, $solicitacao);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$solicitacaoRepository->add($solicitacao, true, $this->getUser());
return $this->redirectToRoute('app_solicitacao_pendentes', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('solicitacao/edit.html.twig', [
'solicitacao' => $solicitacao,
'form' => $form,
'nav_active' => 'Solicitação',
]);
}
#[Route('/aprovar/{id}', name: 'app_solicitacao_aprovar', methods: ['POST'])]
public function aprovar(
Request $request,
Solicitacao $solicitacao,
SolicitacaoRepository $solicitacaoRepository,
Watermark $watermark,
Mailer $mailer,
LoggerInterface $logger
): Response {
$isSuperUser = $this->isGranted('ROLE_SUPER');
$status = $isSuperUser ? Solicitacao::STATUS_ADMINISTRADOR_OK : Solicitacao::STATUS_APROVADOR_OK;
// calculo os redirects de saída (e referer) logo no início
$empresaId = $solicitacao->getEmpresa()?->getId();
$subEmpresaId = $solicitacao->getSubEmpresa()?->getId();
$referer = (string) $request->headers->get('referer');
try {
if ($this->isCsrfTokenValid('aprovar' . $solicitacao->getId(), (string) $request->request->get('_token'))) {
$now = new \DateTimeImmutable('now');
$solicitacao->setStatus($status);
$solicitacao->setUpdatedAt($now);
$solicitacao->setApprovedAt($now);
$comentario = $request->request->get('comentario');
if (is_string($comentario) && trim($comentario) !== '') {
$solicitacao->setComentario(trim($comentario));
}
if ($isSuperUser) {
$solicitacao->setAdministrador($this->getUser());
} else {
$solicitacao->setAprovador($this->getUser());
}
// 1) persiste primeiro (minimiza risco de 500 por side-effects depois)
$solicitacaoRepository->update($solicitacao, true);
// 2) watermark (só se SUPER) – isolado
if ($isSuperUser) {
try {
$watermark->addWatermark($solicitacao, $this->getParameter('kernel.project_dir'), $this->getUser());
} catch (\Throwable $e) {
$logger->error('Erro ao gerar watermark da solicitacao ' . $solicitacao->getId() . ': ' . $e->getMessage(), ['exception' => $e]);
$this->addFlash('error', 'Aprovado, mas ocorreu um erro ao gerar o carimbo d’água.');
}
}
// 3) e-mail – isolado
try {
$mailer->sendMail($solicitacao);
} catch (\Throwable $e) {
$logger->error('Erro ao enviar e-mail da solicitacao ' . $solicitacao->getId() . ': ' . $e->getMessage(), ['exception' => $e]);
$this->addFlash('error', 'Aprovado, mas ocorreu um erro ao enviar as notificações.');
}
$this->addFlash('success', $isSuperUser ? 'Solicitação Aprovada com sucesso.' : 'Solicitação Pré-Aprovada com sucesso.');
} else {
$this->addFlash('error', 'Token inválido. Recarregue a página e tente novamente.');
}
} catch (\Throwable $e) {
// Qualquer erro inesperado vira flash + log, sem 500
$logger->error('Falha no fluxo de aprovação da solicitacao ' . $solicitacao->getId() . ': ' . $e->getMessage(), ['exception' => $e]);
$this->addFlash('error', 'Não foi possível concluir a aprovação: ' . $e->getMessage());
}
// Redirect final (SUPER → empresa; aprovador → subempresa). Fallback: referer.
if ($isSuperUser && $empresaId) {
return $this->redirect(sprintf('/aprovador/empresa/%d', $empresaId));
}
if (!$isSuperUser && $subEmpresaId) {
return $this->redirectToRoute(
'app_aprovador_subempresa_status',
['subEmpresaId' => $subEmpresaId],
Response::HTTP_SEE_OTHER
);
}
// fallback absoluto
return $referer ? new RedirectResponse($referer) : $this->redirectToRoute('app_solicitacao_pendentes');
}
#[Route('/recusar/{id}', name: 'app_solicitacao_recusar', methods: ['POST'])]
public function recusar(
Request $request,
Solicitacao $solicitacao,
SolicitacaoRepository $solicitacaoRepository,
LoggerInterface $logger
): Response {
$isSuperUser = $this->isGranted('ROLE_SUPER');
$empresaId = $solicitacao->getEmpresa()?->getId();
$subEmpresaId = $solicitacao->getSubEmpresa()?->getId();
$referer = (string) $request->headers->get('referer');
try {
if ($this->isCsrfTokenValid('recusar' . $solicitacao->getId(), (string) $request->request->get('_token'))) {
$now = new \DateTimeImmutable('now');
$solicitacao->setUpdatedAt($now);
$solicitacao->setRecusedAt($now);
$solicitacao->setRecusador($this->getUser());
$solicitacao->setStatus(
$isSuperUser ? Solicitacao::STATUS_ADMINISTRADOR_RECUSADO : Solicitacao::STATUS_APROVADOR_RECUSADO
);
$solicitacao->setRecusa((string) $request->request->get('recusa'));
$solicitacaoRepository->update($solicitacao, true);
$this->addFlash('success', 'Solicitação recusada com sucesso.');
} else {
$this->addFlash('error', 'Token inválido. Recarregue a página e tente novamente.');
}
} catch (\Throwable $e) {
$logger->error('Falha ao recusar solicitacao ' . $solicitacao->getId() . ': ' . $e->getMessage(), ['exception' => $e]);
$this->addFlash('error', 'Não foi possível recusar: ' . $e->getMessage());
}
if ($isSuperUser && $empresaId) {
return $this->redirect(sprintf('/aprovador/empresa/%d', $empresaId));
}
if (!$isSuperUser && $subEmpresaId) {
return $this->redirectToRoute(
'app_aprovador_subempresa_status',
['subEmpresaId' => $subEmpresaId],
Response::HTTP_SEE_OTHER
);
}
return $referer ? new RedirectResponse($referer) : $this->redirectToRoute('app_solicitacao_pendentes');
}
#[Route('/{id}', name: 'app_solicitacao_delete', methods: ['POST'])]
public function delete(
Request $request,
Solicitacao $solicitacao,
SolicitacaoRepository $solicitacaoRepository
): Response {
if ($this->isCsrfTokenValid('delete' . $solicitacao->getId(), (string) $request->request->get('_token'))) {
$solicitacaoRepository->remove($solicitacao, true);
}
return $this->redirectToRoute('app_solicitacao_pendentes', [], Response::HTTP_SEE_OTHER);
}
#[Route('/pendentes-count/{empresa}/{status}/{tipo}', methods: ['GET'], defaults: ['empresa' => 'any', 'tipo' => 'any'])]
public function pendentsCount(
Request $request,
SolicitacaoRepository $solicitacaoRepository,
$empresa,
$status,
$tipo
): Response {
if ($empresa !== 'any' && $status !== 'any' && $tipo !== 'any') {
return $this->json($solicitacaoRepository->count(['empresa' => $empresa, 'status' => $status, 'tipo' => $tipo]));
}
if ($empresa !== 'any' && $status !== 'any') {
return $this->json($solicitacaoRepository->count(['empresa' => $empresa, 'status' => $status]));
}
if ($empresa === 'any' && $tipo !== 'any') {
return $this->json($solicitacaoRepository->count(['status' => $status, 'tipo' => $tipo]));
}
return $this->json(['error' => 'Invalid parameters'], Response::HTTP_BAD_REQUEST);
}
/**
* @Route("/report/spreadsheet", name="app_solicitacao_report", methods={"GET"})
*/
public function report(Request $request, SpreadsheetGenerator $spreadsheetGenerator): Response
{
$startDate = $request->query->get('start_date');
$endDate = $request->query->get('end_date');
$reportType = $request->query->get('report_type');
try {
$response = $spreadsheetGenerator->generateExcel($startDate, $endDate, $reportType);
} catch (\Exception $e) {
$this->addFlash('error', 'Ocorreu um erro ao gerar o relatório: ' . $e->getMessage());
return new RedirectResponse((string) $request->headers->get('referer'));
}
return $response;
}
}