<?php
namespace App\Controller\BackOffice;
use App\Entity\Assay\AnalysisMethod;
use App\Entity\Assay\Assay;
use App\Entity\Assay\AssayData;
use App\Entity\Assay\AssayReport;
use App\Entity\Assay\Protocol;
use App\Entity\Customer\Customer;
use App\Entity\Customer\CustomerArea;
use App\Entity\QrCode\QrAssayRelation;
use App\Entity\QrCode\QRData;
use App\Entity\Version\APPVersion;
use App\Form\Type\BackOffice\AnalysisMethodSimulationType;
use App\Form\Type\BackOffice\AssayResultType;
use App\Form\Type\BackOffice\AssaySearchFiltersType;
use App\Model\AssayReportStatus;
use App\Model\AssaySearchCriteria;
use App\Model\PaginatedResource;
use App\Model\PaginatedSearchCriteria;
use App\Service\AnalysisService;
use App\Service\AssayReportService;
use App\Service\AssayResultService;
use App\Service\SinaveService;
use App\ViewModel\BackOffice\AssayDataPoints;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Psr\Log\LoggerInterface;
/**
* Class AssaysController
* @package App\Controller\BackOffice
* @Route(path="assays")
*/
class AssaysController extends AbstractController
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @var FormFactoryInterface
*/
private $formFactory;
/**
* @var AnalysisService
*/
private $analysisService;
/**
* @var AssayResultService
*/
private $assayResultService;
/**
* @var SinaveService
*/
private $sinaveService;
/**
* @var AssayReportService
*/
private $assayReportService;
/**
* @var LoggerInterface
*/
private $logger;
/**
* AssaysController constructor.
* @param EntityManagerInterface $entityManager
* @param FormFactoryInterface $formFactory
* @param AnalysisService $analysisService
* @param AssayResultService $assayResultService
* @param SinaveService $sinaveService
* @param AssayReportService $assayReportService
* @param LoggerInterface $logger
*/
public function __construct(
EntityManagerInterface $entityManager,
FormFactoryInterface $formFactory,
AnalysisService $analysisService,
AssayResultService $assayResultService,
SinaveService $sinaveService,
AssayReportService $assayReportService,
LoggerInterface $logger
) {
$this->entityManager = $entityManager;
$this->formFactory = $formFactory;
$this->analysisService = $analysisService;
$this->assayResultService = $assayResultService;
$this->sinaveService = $sinaveService;
$this->assayReportService = $assayReportService;
$this->logger = $logger;
}
/**
* @Route(
* methods={"GET"},
* defaults={"customerProtocols": false}
* )
* @Route(
* path="/customer",
* methods={"GET"},
* defaults={"customerProtocols": true},
* name="app_backoffice_assays_customer"
* )
* @param Request $request
* @param bool $customerProtocols
* @return Response
*/
public function indexAction(Request $request, bool $customerProtocols): Response
{
$searchFilterForm = $this->formFactory
->create(AssaySearchFiltersType::class);
$searchFilterForm->add("submit", SubmitType::class);
$searchCriteria = new AssaySearchCriteria($request->query);
$searchCriteria->setCustomerProtocols($customerProtocols);
$searchCriteria->setPageSize(PaginatedSearchCriteria::BACKOFFICE_PAGINATION_SIZE);
$searchFilterForm->handleRequest($request);
if ($searchFilterForm->isSubmitted() && $searchFilterForm->isValid()) {
$data = $searchFilterForm->getData();
$searchCriteria->setAssayId($data['assayId']);
$searchCriteria->setSampleId($data['sampleId']);
$searchCriteria->setDate($data['date']);
$searchCriteria->setSku($data['sku']);
$searchCriteria->setBatch($data['batch']);
$searchCriteria->setHardwareVersion($data['hardwareVersion']);
$searchCriteria->setStatus($data['status']);
$searchCriteria->setResult($data['result']);
}
/** @var PaginatedResource $assays */
$assays = $this->entityManager
->getRepository(Assay::class)
->search($searchCriteria);
$customer = null;
if ($searchCriteria->getCustomerId() !== null) {
$customer = $this->entityManager
->getRepository(Customer::class)
->find($searchCriteria->getCustomerId());
}
$protocol = null;
if ($searchCriteria->getProtocolId() !== null) {
$protocol = $this->entityManager
->getRepository(Protocol::class)
->find($searchCriteria->getProtocolId());
}
$qrData = null;
if ($searchCriteria->getQrDataId() !== null) {
$qrData = $this->entityManager
->getRepository(QRData::class)
->find($searchCriteria->getQrDataId());
}
return $this->render(
'backOffice/assays/index.html.twig',
[
'assays' => $assays,
'customer' => $customer,
'protocol' => $protocol,
'qrData' => $qrData,
'filterForm' => $searchFilterForm->createView(),
]
);
}
/**
* @param $assayId
* @return Response
* @Route(path="/{assayId}", methods={"GET"})
*/
public function getAction($assayId): Response
{
/** @var Assay $assay */
$assay = $this->entityManager
->getRepository(Assay::class)
->find($assayId);
if (!$assay) {
throw $this->createNotFoundException();
}
$assayData = $this->entityManager
->getRepository(AssayData::class)
->findByAssay($assay);
$assayDataPoints = AssayDataPoints::create($assayData);
$assayResultForm = $this
->createForm(
AssayResultType::class,
$assay->getResult(),
[
'action' => $this->generateUrl(
'app_backoffice_assayresults_create',
['assayId' => $assay->getAssayId()]
),
]
)
->add('submit', SubmitType::class);
$analysisMethodSimulationForm = $this
->createForm(
AnalysisMethodSimulationType::class,
null,
[
'analysisMethods' => $assay->getProtocol()->getAnalysisMethods()->toArray(),
'action' => $this->generateUrl(
'app_backoffice_assays_simulateanalysis',
['assayId' => $assay->getAssayId()]
),
]
)
->add('submit', SubmitType::class);
$appVersion = $this->entityManager
->getRepository(APPVersion::class)
->findOneBy(["versionCode" => $assay->getSoftwareVersion()]);
$assayReports = $this->entityManager
->getRepository(AssayReport::class)
->findBy(['assay' => $assay], ['createdAt' => 'desc']);
return $this->render(
'backOffice/assays/show.html.twig',
[
'assay' => $assay,
'assayData' => $assayData,
'assayDataPoints' => $assayDataPoints,
'assayResultForm' => $assayResultForm->createView(),
'analysisMethodSimulationForm' => $analysisMethodSimulationForm->createView(),
'appVersion' => $appVersion ? $appVersion->getVersionName() : $assay->getSoftwareVersion(),
'env' => $this->getParameter('kernel.environment'),
'assayReports' => $assayReports,
]
);
}
/**
* @param $assayId
* @return Response
* @Route(path="/{assayId}/downloadReport", methods={"GET"})
*/
public function downloadReport($assayId): Response
{
/** @var Assay $assay */
$assay = $this->entityManager
->getRepository(Assay::class)
->find($assayId);
if (!$assay) {
throw $this->createNotFoundException();
}
$protocol = $assay->getProtocol();
if (!$protocol || !$protocol->getTest() || !$protocol->getTest()->getCertificateTemplate()) {
throw $this->createNotFoundException();
}
$owner = $assay->getUser();
/** @var CustomerArea $clientArea */
$clientArea = $this->entityManager
->getRepository(CustomerArea::class)
->getCustomerAreaForUser($owner);
$customer = $this->entityManager
->getRepository(Customer::class)
->getCustomerForUser($owner);
/** @var QrAssayRelation $relation */
$relation = $this->entityManager
->getRepository(QrAssayRelation::class)
->findByAssay($assayId);
$data = null;
if ($relation) {
/** @var QRData $data */
$data = $this->entityManager
->getRepository(QRData::class)
->find($relation->getQrDataId());
}
try {
$report = $this->assayResultService->renderCertificate($data, $assay, "en_EN", $customer, $clientArea);
$report->stream();
} catch (LoaderError|RuntimeError|SyntaxError $e) {
throw new BadRequestHttpException($e->getMessage());
}
return new Response(null, 200, []);
}
/**
* This is mainly intended for testing purposes.
* In the long run (when the connection is stable) it should be removed.
* If applicable, result notification is done automatically when an assay ends.
* @param $assayId
* @return Response
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
* @throws TransportExceptionInterface
* @throws \Symfony\Component\Mailer\Exception\TransportExceptionInterface
* @Route(
* path="/{assayId}/notifySinave",
* methods={"GET"}
* )
*/
public function notifySinave($assayId): Response
{
/** @var Assay $assay */
$assay = $this->entityManager
->getRepository(Assay::class)
->find($assayId);
if (!$assay) {
throw $this->createNotFoundException();
}
/** @var QrAssayRelation $relation */
$relation = $this->entityManager
->getRepository(QrAssayRelation::class)
->findByAssay($assayId);
if (!$relation) {
$this->addFlash("warning", "Assay not bound to patient data.");
} else {
/** @var QRData $data */
$data = $this->entityManager
->getRepository(QRData::class)
->find($relation->getQrDataId());
$customer = $this->entityManager
->getRepository(Customer::class)
->getCustomerForUser($assay->getUser());
$externalEntityRx = $this->sinaveService->submitAssay($assay, $customer, $data);
$assay->getResult()->setHealthAuthorityRx($externalEntityRx);
$this->entityManager->persist($assay);
$this->entityManager->flush();
}
return $this->redirectToRoute(
'app_backoffice_assays_get',
[
'assayId' => $assay->getAssayId(),
]
);
}
/**
* @param $assayId
* @return Response
* @Route(path="/{assayId}/notifyReporter", methods={"POST"})
*/
public function notifyReporter($assayId): Response
{
/** @var Assay $assay */
$assay = $this->entityManager
->getRepository(Assay::class)
->find($assayId);
if (!$assay) {
throw $this->createNotFoundException();
}
if (!$assay->getReporter()) {
$this->addFlash('warning', 'This assay has no reporter defined');
return $this->redirectToRoute(
'app_backoffice_assays_get',
[
'assayId' => $assay->getAssayId(),
]
);
}
$report = $this->assayReportService->reportAssayAndPersist($assay);
$reporterName = $this->assayReportService->getReporterForIdentifier($assay->getReporter())->getName();
switch ($report !== null ? $report->getStatus() : null) {
case AssayReportStatus::ERROR:
$this->addFlash(
'danger',
sprintf('Assay report to %s failed. Please check below.', $reporterName)
);
break;
case AssayReportStatus::UNKNOWN:
$this->addFlash(
'warning',
sprintf('Assay report to %s did not complete. Please check below.', $reporterName)
);
break;
case AssayReportStatus::SUCCESS:
$this->addFlash(
'success',
'Assay report to %s completed.'
);
break;
}
return $this->redirectToRoute(
'app_backoffice_assays_get',
[
'assayId' => $assay->getAssayId(),
]
);
}
/**
* @param Request $request
* @param $assayId
* @return Response
* @Route(path="/{assayId}/simulateAnalysis", methods={"POST"})
*/
public function simulateAnalysis(Request $request, $assayId): Response
{
/** @var Assay $assay */
$assay = $this->entityManager
->getRepository(Assay::class)
->find($assayId);
if (!$assay) {
throw $this->createNotFoundException();
}
if ($assay->getSkipAnalysis()) {
throw new BadRequestHttpException("This assay cannot be analyzed");
}
$form = $this
->createForm(
AnalysisMethodSimulationType::class,
null,
[
'action' => $this->generateUrl(
'app_backoffice_assays_simulateanalysis',
['assayId' => $assay->getAssayId()]
),
]
)
->add('submit', SubmitType::class);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->forward(
AssaysController::class.'::'.'getAction',
[
'assayId' => $assay->getAssayId(),
]
);
}
/** @var AnalysisMethod $analysisMethod */
$analysisMethod = $form->get('analysisMethod')->getData();
$assayData = $assay->getAssayData();
$context = $this->analysisService->analyzeAssayWithMethod(
$assay,
$assayData->toArray(),
$analysisMethod
);
return $this->render(
'backOffice/assays/simulateAnalysis.html.twig',
[
'assay' => $assay,
'assayData' => $assayData,
'analysisMethod' => $analysisMethod,
'assayResult' => $context->getAssayResult(),
]
);
}
}