<?php
namespace App\Controller\Gauge;
use App\Controller\AppBaseController;
use App\Entity\Admin\RemoteEndPoint;
use App\Entity\Building\Building;
use App\Entity\Client\Client;
use App\Entity\Gauge\Gauge;
use App\Entity\Gauge\GaugeLimitOverflow;
use App\Entity\Gauge\Supplier;
use App\Entity\User;
use App\Form\Type\Gauge\GaugeCreateFormType;
use App\Form\Type\Gauge\GaugeEditFormType;
use App\Form\Type\Gauge\GaugeEditLevelFormType;
use App\Repository\Gauge\GaugeRepository;
use App\Service\Event\AutoEventGenerator;
use App\Service\File\FileBuildingSync;
use App\Service\Gauge\QrCodeGeneratorService;
use App\Service\Output\HeatingPowerManager;
use App\Service\Output\RemoteReadingsMiner;
use App\Message\UpdateAllReadingsDailyStatusForGauge;
use App\Service\TransHelper;
use App\Service\User\UserSettingsManager;
use App\Service\VueExportHelper;
use App\Service\VueListHelper;
use App\Utils\DateFormat;
use App\Utils\GaugeType;
use App\Utils\Period;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
class GaugeController extends AppBaseController
{
/**
* @Route("/building/{buildingId}/gauge-list", name="gauge_list")
* @ParamConverter("building", class="App\Entity\Building\Building", options={"id" = "buildingId"})
* @param Request $request
* @param Building $building
* @param VueListHelper $listHelper
* @return Response
* @throws \Exception
*/
public function list(Request $request, Building $building, VueListHelper $listHelper): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_SEE_GAUGE_LIST');
$this->denyAccessUnlessGrantedBuilding('view', $building);
$returnUrl = $request->get('returnUrl');
if (strpos($returnUrl, $this->router->generate('building_list')) === false) {
$returnUrl = $this->router->generate('building_list');
}
/** @var GaugeRepository $gaugeRepository */
$gaugeRepository = $this->entityManager->getRepository(Gauge::class);
$gaugeRepository->setBuilding($building);
$listData = $listHelper->serializeCustomAppListDefiner($gaugeRepository);
$this->logger->log('page', $request, null, ['name' => $building->getName()]);
return $this->render('app/gauge/list.html.twig', [
'building' => $building,
'listData' => $listData,
'disabledTitle' => $this->translator->trans('list.title.disabled', ['%building%' => $building->getName()], 'gauge'),
'listDisabledData' => $listHelper->getSerializedDisabled(Gauge::class, $gaugeRepository),
'returnUrl' => $returnUrl
]);
}
/**
* @Route("building/{buildingId}/gauge/export", name="gauge_export")
* @ParamConverter("building", class="App\Entity\Building\Building", options={"id" = "buildingId"})
* @param Request $request
* @param Building $building
* @param VueExportHelper $helper
* @return Response
*/
public function export(Request $request, Building $building, VueExportHelper $helper): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_EXPORT_GAUGE');
$this->denyAccessUnlessGrantedBuilding('view', $building);
$data = [
'term' => $request->query->getAlnum('searchTerm'),
'on' => $request->query->get('searchOn'),
'sortBy' => $request->query->get('sortBy'),
'sortDirection' => $request->query->get('sortDirection'),
'ids' => $request->get('filteredItems'),
];
$this->logger->log('gauge.exported', $building, $data['term'], $data['on']);
/** @var GaugeRepository $gaugeRepos */
$gaugeRepos = $this->entityManager->getRepository(Gauge::class);
$gaugeRepos->setBuilding($building);
$gaugeRepos->setIsExport(true);
$listData = $gaugeRepos->getAppListItems();
$title = $this->translator->trans('page.title.list', ['%building%' => $building->getName()], 'gauge');
$safeTitle = preg_replace('/[\/\\\:*?"<>|]/', '_', $title);
$filename = $safeTitle.'_'. date('Y-m-d') . '.xlsx';
try {
return $this->file($helper->getExportFile(Gauge::class, $data, $listData), $filename);
} catch (\Exception $e) {
$this->addFlash(
'error',
$this->translator->trans('message.cannot_generate_export', [], 'common')
);
return $this->redirectToRoute('gauge_list', ['buildingId' => $building->getId()]);
}
}
/**
* @Route("/building/{buildingId}/gauge/create", name="gauge_create")
* @ParamConverter("building", class="App\Entity\Building\Building", options={"id" = "buildingId"})
*
* @param Request $request
* @param Building $building
* @param TransHelper $transHelper
* @param HeatingPowerManager $heatingPowerManager
* @return Response
*/
public function create(
Request $request,
Building $building,
TransHelper $transHelper,
HeatingPowerManager $heatingPowerManager,
QrCodeGeneratorService $qrCodeGenerator
): Response {
$this->denyAccessUnlessGranted('ROLE_CAN_CREATE_GAUGE');
$this->denyAccessUnlessGrantedBuilding('view', $building);
$gauge = new Gauge();
$gauge->setBuilding($building);
$form = $this->createForm(GaugeCreateFormType::class, $gauge);
if ($request->isMethod('POST')) {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->log('gauge.created', $request->request->get('gauge_create_form'));
/**
* @var Gauge $gauge
*/
$gauge = $form->getData();
// check unknown heating power type
$heatingPowerManager->checkAndProcessHeatingPowerCase($gauge);
$result = $this->processCreateUpdate(
$gauge,
$this->generateUrl('gauge_list', ['buildingId' => $building->getId()]),
$this->translator->trans(
'message.gauge_created',
[],
'gauge'
)
);
// Generate QR code for the newly created gauge
try {
$qrPath = $qrCodeGenerator->generateQrCodeForGauge($gauge);
if ($qrPath !== null) {
$gauge->setQrCodePath($qrPath);
$this->entityManager->persist($gauge);
$this->entityManager->flush();
}
} catch (\Exception $e) {
// QR generation failure shouldn't break gauge creation
// Log the error but continue with the response
error_log('QR code generation failed for gauge ' . $gauge->getId() . ': ' . $e->getMessage());
}
$this->entityManager->getRepository(Supplier::class)->updateUsages();
return $result;
}
return $this->errorResponse($form);
}
return $this->render('app/gauge/new.html.twig', [
'form' => $this->formSerializer->serialize($form, ['groups' => 'create']),
'building' => $building,
'translations' => $transHelper->getGaugeFormTranslations()
]);
}
/**
* @Route("/gauge/edit/{id}", name="gauge_edit")
* @ParamConverter("gauge", class="App\Entity\Gauge\Gauge")
* @param Gauge $gauge
* @param Request $request
* @param TransHelper $transHelper
* @param HeatingPowerManager $heatingPowerManager
* @return Response
*/
public function edit(Gauge $gauge, Request $request, TransHelper $transHelper, HeatingPowerManager $heatingPowerManager, FileBuildingSync $fileBuildingSync, MessageBusInterface $messageBus): Response
{
$this->denyAccessUnlessGrantedGauge('view', $gauge);
$originalRemoteAsPrimary = $gauge->isRemoteAsPrimary();
// EM-838: Snapshot building before form may change it
$originalBuilding = $gauge->getBuilding();
$form = $this->createForm(GaugeEditFormType::class, $gauge);
if ($request->isMethod('POST')) {
$this->denyAccessUnlessGrantedGauge('edit', $gauge);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->log('gauge.updated', $gauge, $request->request->get('gauge_edit_form'));
/**
* @var Gauge $gauge
*/
$gauge = $form->getData();
// check unknown heating power type
$heatingPowerManager->checkAndProcessHeatingPowerCase($gauge);
// EM-838: Update file_building for all associated files when building changed
if ($originalBuilding->getId() !== $gauge->getBuilding()->getId()) {
$fileBuildingSync->updateFileBuildingsForGaugeMove($gauge, $originalBuilding);
}
$result = $this->processCreateUpdate(
$gauge,
$this->generateUrl('gauge_list', ['buildingId' => $gauge->getBuilding()->getId()]),
$this->translator->trans('message.gauge_updated', [], 'gauge')
);
$this->entityManager->getRepository(Supplier::class)->updateUsages();
if ($gauge->isRemoteAsPrimary() !== $originalRemoteAsPrimary) {
$messageBus->dispatch(new UpdateAllReadingsDailyStatusForGauge($gauge->getId()));
}
return $result;
}
// return bag of errors
return $this->errorResponse($form);
}
$this->logger->log('gauge.detail', $gauge);
return $this->render('app/gauge/edit.html.twig', [
'gauge' => $gauge,
'form' => $this->formSerializer->serialize($form, ['groups' => 'edit']),
'translations' => $transHelper->getGaugeFormTranslations()
]);
}
/**
* @Route("/gauge/edit-level/{id}", name="gauge_edit_level")
* @ParamConverter("gauge", class="App\Entity\Gauge\Gauge")
* @param Gauge $gauge
* @param Request $request
* @param TransHelper $transHelper
* @return Response
*/
public function editLevel(Gauge $gauge, Request $request, TransHelper $transHelper, FileBuildingSync $fileBuildingSync): Response
{
$this->denyAccessUnlessGrantedGauge('edit', $gauge);
$this->denyAccessUnlessGranted('ROLE_ADMIN');
// EM-838: Snapshot building before form may change it
$originalBuilding = $gauge->getBuilding();
$form = $this->createForm(GaugeEditLevelFormType::class, $gauge);
if ($request->isMethod('POST')) {
$this->denyAccessUnlessGrantedGauge('edit', $gauge);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->log('gauge.updated', $gauge, $request->request->get('gauge_edit_level_form'));
/**
* @var Gauge $gauge
*/
$gauge = $form->getData();
// EM-838: Update file_building for all associated files when building changed
if ($originalBuilding->getId() !== $gauge->getBuilding()->getId()) {
$fileBuildingSync->updateFileBuildingsForGaugeMove($gauge, $originalBuilding);
}
return $this->processCreateUpdate(
$gauge,
$this->generateUrl('gauge_list', ['buildingId' => $gauge->getBuilding()->getId()]),
$this->translator->trans('message.gauge_updated', [], 'gauge')
);
}
// return bag of errors
return $this->errorResponse($form);
}
$this->logger->log('gauge.detail', $gauge);
return $this->render('app/gauge/edit_level.html.twig', [
'gauge' => $gauge,
'form' => $this->formSerializer->serialize($form, ['groups' => 'edit']),
'translations' => $transHelper->getGaugeFormTranslations()
]);
}
/**
* @Route("/building/{buildingId}/gauge/list-settings", name="gauge_list_settings")
* @ParamConverter("building", class="App\Entity\Building\Building", options={"id" = "buildingId"})
*
* buildingId is compulsory for return button and route after update
*
* @param UserSettingsManager $userSettingsManager
* @param Building $building
* @param TransHelper $transHelper
* @param Request $request
* @return Response
*/
public function listSettings(
Request $request,
Building $building,
UserSettingsManager $userSettingsManager,
TransHelper $transHelper
): Response {
$this->denyAccessUnlessGranted('ROLE_CAN_SEE_GAUGE_LIST');
$this->denyAccessUnlessGrantedBuilding('view', $building);
/** @var User $user */
$user = $this->getUser();
if ($request->isMethod('POST')) {
$requestData = $request->request->all();
$userSettingsManager->saveUserListFieldsByRequestData($user, $requestData, 'gauge');
$this->logger->log('gauge.list_settings_updated', $requestData);
return $this->processCreateUpdate(
$user,
$this->generateUrl('gauge_list', ['buildingId' => $building->getId()]),
$this->translator->trans('message.list_settings.success', [], 'gauge')
);
}
$client = $building->getClient();
$allowedFields = $userSettingsManager->getAllowedFields('gauge', $client);
$userFields = $userSettingsManager->getUserListFields($user, 'gauge', $client);
$this->logger->log('gauge.list_settings_detail');
return $this->render('app/gauge/settings.html.twig', [
'building' => $building,
'allowedFields' => $allowedFields,
'userFields' => $userFields,
'translations' => $transHelper->getDefaultUserSettingsTranslations()
]);
}
/**
* @Route("/gauge/disable/{id}", name="gauge_disable")
* @ParamConverter("gauge", class="App\Entity\Gauge\Gauge")
*
* @param Request $request
* @param Gauge $gauge
* @param AutoEventGenerator $autoEventGenerator
* @return Response
*/
public function disable(Request $request, Gauge $gauge, AutoEventGenerator $autoEventGenerator): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_DISABLE_GAUGE');
/** @var User $user */
$user = $this->getUser();
/** @var int|null $disabledReason */
$disabledReason = $request->query->get('disabledReason');
/** @var bool|null $showInOutput */
$showInOutput = $request->query->get('showInOutput');
$gauge->disable($user, $disabledReason, $showInOutput);
try {
$this->entityManager->persist($gauge);
$this->entityManager->flush();
$autoEventGenerator->processGaugeDisabled($gauge, false);
} catch (\Exception $exception) {
$this->addFlash('error', $this->translator->trans('message.gauge_disabled.error', [], 'gauge'));
}
$this->logger->log('page', $request, null, ['name' => $gauge->getName()]);
$this->addFlash('success', $this->translator->trans('message.gauge_disabled.success', [], 'gauge'));
return $this->redirectToRoute('gauge_list', ['buildingId' => $gauge->getBuilding()->getId()]);
}
/**
* @Route("/gauge/enable/{id}", name="gauge_enable")
* @ParamConverter("gauge", class="App\Entity\Gauge\Gauge")
*
* @param Request $request
* @param Gauge $gauge
* @return Response
*/
public function enable(Request $request, Gauge $gauge): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_ENABLE_GAUGE');
$gauge->enable();
try {
$this->entityManager->persist($gauge);
$this->entityManager->flush();
} catch (\Exception $exception) {
$this->addFlash('error', $this->translator->trans('message.gauge_enabled.error', [], 'gauge'));
}
$this->logger->log('page', $request, null, ['name' => $gauge->getName()]);
$this->addFlash('success', $this->translator->trans('message.gauge_enabled.success', [], 'gauge'));
return $this->redirectToRoute('gauge_list', ['buildingId' => $gauge->getBuilding()->getId()]);
}
/**
* @Route("/gauge/delete/{id}", name="gauge_delete")
* @ParamConverter("user", class="App\Entity\Gauge\Gauge")
* @param Gauge $gauge
* @return Response
*/
public function delete(Gauge $gauge): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_DELETE_GAUGE');
$buildingId = $gauge->getBuilding()->getId();
try {
if ($gauge->getReadings()->count() > 0 || $gauge->getInvoices()->count() > 0) {
$this->addFlash('error', $this->translator->trans('message.gauge_has_data.error', [], 'gauge'));
return $this->redirectToRoute('gauge_list', ['buildingId' => $buildingId]);
}
if ($gauge->getChildren()->count() > 0) {
$this->addFlash('error', $this->translator->trans('message.gauge_has_children.error', [], 'gauge'));
return $this->redirectToRoute('gauge_list', ['buildingId' => $buildingId]);
}
/** @var User $user */
$user = $this->getUser();
if ($gauge->getImport() !== null) {
$gauge->getImport()->addWarning([
'gaugeId' => $gauge->getId(),
'text' => $this->translator->trans('gauge.warning.deleted', [
'%user%' => $user->getName(),
'%identifier%' => $gauge->getProperties()->getIdentifier(),
'%date%' => (new \DateTime())->format(DateFormat::NORMALIZED),
'%gaugeId%' => $gauge->getId(),
], 'import')
]);
}
$this->logger->log('gauge.deleted', $gauge);
$this->entityManager->remove($gauge);
$this->entityManager->flush();
$this->addFlash('success', $this->translator->trans('message.gauge_deleted.success', [], 'gauge'));
} catch (\Exception $exception) {
$this->addFlash('error', $this->translator->trans('message.gauge_deleted.error', [], 'gauge'));
}
return $this->redirectToRoute('gauge_list', ['buildingId' => $buildingId]);
}
/**
* @Route("/api/gauge/choices-by-client/{id}", name="api_gauge_choices_by_client")
* @ParamConverter("client", class="App\Entity\Client\Client")
*
* @param Client $client
* @return Response
*/
public function getChoicesByClient(Client $client): Response
{
$this->denyAccessUnlessGranted('ROLE_CAN_EDIT_USER');
$choices = [];
foreach ($client->getBuildings() as $building) {
foreach ($building->getGauges() as $gauge) {
$choices[] = [
'html' => $gauge->getNameWithBuilding(),
'value' => (string)$gauge->getId(),
];
}
}
return new JsonResponse($choices);
}
/**
* @Route("/gauge/{id}/limit-overflow", name="gauge_limit_overflow_list")
* @ParamConverter("gauge", class="App\Entity\Gauge\Gauge")
*
* @param Gauge $gauge
* @param VueListHelper $listHelper
* @return Response
* @throws \Exception
*/
public function distributionRateOptimize(Gauge $gauge, VueListHelper $listHelper): Response
{
$this->denyAccessUnlessGrantedGauge('view', $gauge);
$repos = $this->entityManager->getRepository(GaugeLimitOverflow::class);
$repos->setGauge($gauge);
return $this->render('app/generic/list.html.twig', [
'listData' => $listHelper->serializeCustomAppListDefiner($repos),
'pageTitle' => $this->translator->trans('page.title.gauge_limit_overflow', array('%gauge%' => $gauge->getName()), 'gauge'),
'topButtons' => [[
'label' => $this->translator->trans('button.label.back_to_gauge_detail', [], 'gauge'),
'url' => $this->generateUrl('gauge_edit', ['id' => $gauge->getId()]),
'class' => 'btn-secondary'
]]
]);
}
/**
* @Route("/api/gauge-remote-endpoint-period/{id}", name="api_remote_endpoint_period")
*
* @param int $id
* @param RemoteReadingsMiner $remoteReadingsMiner
* @return Response
*/
public function getActionCategoryChoicesByParent(int $id, RemoteReadingsMiner $remoteReadingsMiner, GaugeType $gaugeType): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
/** @var User $loggedUser */
$loggedUser = $this->getUser();
// $remoteEndpointRepos = $this->entityManager->getRepository(RemoteEndPoint::class);
// $allowedEndpoints = $remoteEndpointRepos->getEndpointsForPairingInApp($loggedUser->getClient());
//
// if (!array_key_exists($id, $allowedEndpoints)) {
// throw $this->createNotFoundException('Not found or allowed RemoteEndPoint');
// }
$frequencyInMinutes = $remoteReadingsMiner->getMinutesFrequencyByLastRecords($id);
return new JsonResponse([
'minutes' => $frequencyInMinutes,
'period' => $gaugeType->getFormattedPeriodByMinutes($frequencyInMinutes),
]);
}
}