<?php
namespace App\Controller;
use Carbon\Carbon;
use App\Entity\Job;
use ReflectionClass;
use App\Entity\Import;
use App\Entity\Snapshot;
use App\Entity\MealPeriod;
use App\Entity\ImportOrderSummary;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Routing\Annotation\Route;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
* Class JobController
* @package App\Controller
*/
class JobController extends AbstractController
{
/**
* @var int
*/
public const LIMIT_JOB_EXECUTION_COUNT = 3;
/**
* starts processing jobs in background process
* @throws \Exception
*/
public static function triggerProcessingJobs(KernelInterface $kernel)
{
// trigger processing jobs
$application = new Application($kernel);
$application->setAutoExit(false);
$input = new ArrayInput([
'command' => 'app:process-jobs'
]);
// You can use NullOutput() if you don't need the output
$output = new NullOutput();
$application->run($input, $output);
}
/**
* @Route("/job/displayed", name="job_displayed")
* @IsGranted("ROLE_USER")
* @param \Symfony\Component\HttpFoundation\Request $request
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function setJobDisplayed(Request $request): JsonResponse
{
$job_id = $request->get("job_id", false);
if (!$job_id) {
return new JsonResponse([
"result" => false,
"message" => "No Job ID.",
"data" => [],
]);
}
$entityManager = $this->getDoctrine()->getManager();
$job = $entityManager->getRepository(Job::class)->find($job_id);
if (!preg_match("/_displayed$/", $job->getStatus())) {
$status = $job->getStatus() . "_displayed";
$job->setStatus($status);
$job->setModified(new \DateTime());
$entityManager->persist($job);
$entityManager->flush();
}
return new JsonResponse([
"result" => true,
"message" => "Job set displayed successfully",
"data" => [],
]);
}
/**
* @Route("/job/ready", name="job_ready")
* @IsGranted("ROLE_USER")
* @param Request $request
* @param \Symfony\Component\HttpKernel\KernelInterface $kernel
* @return JsonResponse
* @throws \Exception
*/
public function getReadyJobs(Request $request, KernelInterface $kernel): JsonResponse
{
$user_id = $request->get("user_id", false);
if (!$user_id) {
return new JsonResponse([
"result" => false,
"message" => "No User ID.",
"data" => [],
]);
}
$entityManager = $this->getDoctrine()->getManager();
$jobs = $entityManager->getRepository(Job::class)->findBy([
"created_by" => $user_id,
"status" => ["done", "failed"],
], [
"created" => "ASC",
]);
$responseData = [];
foreach ($jobs as $job) {
$responseData[] = [
"job_id" => $job->getId(),
"job_url" => $job->getUrl(),
"job_type" => $job->getType(),
"job_status" => $job->getStatus(),
"job_finished" => $job->getFinished()->getTimestamp(),
];
}
JobController::triggerProcessingJobs($kernel);
return new JsonResponse([
"result" => true,
"message" => "Jobs loaded successfully",
"data" => $responseData,
]);
}
/**
* @Route("/job/current", name="job_current")
* @IsGranted("ROLE_USER")
* @param Request $request
* @param KernelInterface $kernel
* @return JsonResponse
*/
public function getByCurrentUser(Request $request, KernelInterface $kernel): JsonResponse
{
$user = $this->getUser();
$user_role = $user->getUserRole();
$project_id = $request->get("project_id", false);
$import_id = $request->get("import_id", false);
$entityManager = $this->getDoctrine()->getManager();
# Get import order summary via entity manager
$importOrderSummary = $entityManager->getRepository(ImportOrderSummary::class)->findBy(
array(
"project_id" => $project_id,
"import_id" => $import_id,
),
array(
"created" => "DESC"
)
);
$queryBuilder = $entityManager->createQueryBuilder();
$snapshots = $queryBuilder->select('s')
->from(Snapshot::class, 's')
->where('s.project_id = :project_id')
->andWhere('s.import_id = :import_id');
$snapshots = $snapshots->orderBy('s.created', 'DESC')
->setParameters([
'project_id' => $project_id,
'import_id' => $import_id,
])
->getQuery()
->getResult();
$filteredJobs = [];
foreach ($snapshots as $snapshot) {
if ($user_role == 2 && $snapshot->getDeleted() == true) {
continue;
}
$s_content = unserialize(base64_decode($snapshot->getContent()));
$s_project = $s_content["project"];
$s_project_id = $s_content["project"]->getId();
$s_import = $s_content["import"];
$s_import_id = $s_content["import"]->getId();
$s_type = $s_content["snapshot_type"];
$s_category = $s_content["category_selected"] ?? "";
$s_super_category = $s_content["super_category_selected"] ?? "";
$s_meal_period = $s_content["meal_period_selected"] ?? "";
// http://localhost:2222/dashboard/overall?project_id=21&import_id=203
if ($s_content["snapshot_type"] == "overall") {
// Generate hash params
$getVars = [];
$getVars["project_id"] = strval($s_project_id);
$getVars["import_id"] = strval($s_import_id);
} else if ($s_content["snapshot_type"] == "details") {
$s_super_category = $s_content["super_category_selected"];
$s_category = $s_content["category_selected"];
$s_meal_period_id = $s_content["meal_period_id_selected"];
$s_project_id = strval($s_project_id);
$s_import_id = strval($s_import_id);
$s_days = $s_content["days"];
// Generate hash params
$getVars = [];
$getVars["super_category"] = $s_super_category;
$getVars["category"] = $s_category;
$getVars["meal_period_id"] = $s_meal_period_id;
$getVars["project_id"] = $s_project_id;
$getVars["import_id"] = $s_import_id;
if ($s_days["sunday"] != 0) $getVars["sunday"] = $s_days["sunday"];
if ($s_days["monday"] != 0) $getVars["monday"] = $s_days["monday"];
if ($s_days["tuesday"] != 0) $getVars["tuesday"] = $s_days["tuesday"];
if ($s_days["wednesday"] != 0) $getVars["wednesday"] = $s_days["wednesday"];
if ($s_days["thursday"] != 0) $getVars["thursday"] = $s_days["thursday"];
if ($s_days["friday"] != 0) $getVars["friday"] = $s_days["friday"];
if ($s_days["saturday"] != 0) $getVars["saturday"] = $s_days["saturday"];
}
// We need to get the import from the database, because the import in the snapshot is not the same as the one in the database
$currentImport = $entityManager->getRepository(Import::class)->find($s_import_id);
$currentImportOrderSummary = $entityManager->getRepository(ImportOrderSummary::class)->findBy([
"project_id" => $s_project_id,
"import_id" => $s_import_id,
]);
$currentHashParams = hash("sha256", serialize([$getVars, $currentImport, $currentImportOrderSummary]));
$currentHashParams2 = $currentHashParams;
if ($s_content["snapshot_type"] == "details") {
/*
We kind of need this extra variable to make sure the hash is the same as the one in the snapshot - sometimes?
For know we accept both with and without the submit-form-button as unchanged.
Maybe we can remove this in the future and always include this (at line 229).
*/
$getVars["submit-form-button"] = "";
$currentHashParams2 = hash("sha256", serialize([$getVars, $currentImport, $currentImportOrderSummary]));
}
$s_hash = $snapshot->getHash();
$s_hashParams = $snapshot->getHashParams();
$s_startDate = $s_content["import"]->getStartDate();
$s_endDate = $s_content["import"]->getEndDate();
$s_startDateAnalysis = $s_content["import"]->getStartDateAnalysis();
$s_endDateAnalysis = $s_content["import"]->getEndDateAnalysis();
$s_overheadCosts = $s_content["import"]->getOverheadCosts(); // Betriebskoste
$s_CogsNet = $s_content["import"]->getDefaultCogsNet(); // Wareneinsat
$s_categories = $s_content["categories"];
$s_days = $s_content["days"];
$s_snapshot_type = $s_content["snapshot_type"];
$_deleted = $snapshot->getDeleted();
$s_baseUrl = $request->getSchemeAndHttpHost() . $request->getBaseUrl();
$softChange = ($s_import->getOverheadCosts() != $s_overheadCosts
|| $s_import->getStartDate() != $s_startDate
|| $s_import->getEndDate() != $s_endDate
|| $s_import->getStartDateAnalysis() != $s_startDateAnalysis
|| $s_import->getEndDateAnalysis() != $s_endDateAnalysis
|| $s_import->getDefaultCogsNet() != $s_CogsNet);
$changed = ($currentHashParams != $s_hashParams && $currentHashParams2 != $s_hashParams) ? "outdated" : "";
Carbon::setLocale('de');
$outdatedParams = [
"Overhead costs" => ($currentImport->getOverheadCosts() != $s_overheadCosts) ? $s_overheadCosts : false,
"Startdate of Import" => ($currentImport->getStartDate() != $s_startDate) ? Carbon::parse($s_startDate->date)->toDateTimeString() : false,
"EndDate of Import" => ($currentImport->getEndDate() != $s_endDate) ? Carbon::parse($s_endDate->date)->toDateTimeString() : false,
"Startdate of Analysis" => ($currentImport->getStartDateAnalysis() != $s_startDateAnalysis) ? Carbon::parse($s_startDateAnalysis->date)->toDateTimeString() : false,
"Enddate of Analysis" => ($currentImport->getEndDateAnalysis() != $s_endDateAnalysis) ? Carbon::parse($s_endDateAnalysis->date)->toDateTimeString() : false,
"Default Cogs net" => ($currentImport->getDefaultCogsNet() != $s_CogsNet) ? $s_CogsNet : false,
];
$outdatedParamsString = "";
$outdatedParamsArray = array_filter($outdatedParams, function($value) {
return $value === true;
});
if (!empty($outdatedParamsArray)) {
$outdatedParamsString = implode(", ", array_keys($outdatedParamsArray));
}
// $viewData = array(
// "project" => $project,
// "import" => $import,
// "cash_system" => $cash_system,
// "meal_periods" => $meal_periods,
// "categories" => $categories,
// "analysis_data" => $analysis_data,
// "analysis_data_charts" => $analysis_data_charts,
// "kpis_total" => $kpis_total,
// "pie_chart_data" => $pie_chart_data,
// "column_chart_data" => $column_chart_data,
// "notice" => $notice,
// "snapshot" => false,
// "snapshot_id" => false,
// "snapshots" => [],
// "snapshot_contents" => [],
// "snapshot_type" => "overall",
// "currency" => $currency,
// "days" => $days,
// );
$d = $snapshot->getCreated()->format("d.m.Y H:i:s");
$newJob["id"] = 1;
//$newJob["url"] = "$s_baseUrl/dashboard/$s_type?project_id=$s_project_id&import_id=$s_import_id";
$newJob["url"] = "$s_baseUrl/dashboard/$s_type?hash=$s_hash";
$newJob["hash"] = $s_hash;
$newJob["date"] = date("d.m.Y", strtotime($d) );
$newJob["time"] = date("H:i:s", strtotime($d) );
$newJob["type"] = $s_snapshot_type;
$newJob["status"] = $changed;
$newJob["outdatedParams"] = $outdatedParams;
$newJob["days"] = $s_days;
$newJob["importname"] = $s_import->getDescription();
$newJob["category"] = $s_category;
$newJob["super_category"] = $s_super_category;
$newJob["meal_period"] = $s_meal_period;
$newJob["project_id"] = $s_project_id;
$newJob["import_id"] = $s_import_id;
$newJob["deleted"] = $_deleted;
// $newJob["meal_period"] = $s_meal_period_name;
$filteredJobs[] = $newJob;
}
return new JsonResponse([
"result" => true,
"message" => "Jobs loaded successfully",
"data" => $filteredJobs,
]);
}
/**
* @Route("/job/process", name="job_process")
* @param Request $request
* @param \Symfony\Component\HttpKernel\KernelInterface $kernel
* @return Response
* @throws \Exception
*/
public function process(Request $request, KernelInterface $kernel): Response
{
$entityManager = $this->getDoctrine()->getManager();
// don't do anything if a job is running already
$runningJobs = $entityManager->getRepository(Job::class)->findBy([
"status" => "processing"
]);
$now = Carbon::now();
$freedJobs = 0;
foreach ($runningJobs as $j) {
// It is possible that jobs get "stuck". if a Jobs is longer then 1 Minute on "processing",
// set the status to "notfinished".
$startet = Carbon::parse($j->getCreated());
if ($now->diffInSeconds($startet) > 60) {
$crashedJob = $entityManager->getRepository(Job::class)->find($j->getId());
$crashedJob->setStatus("notfinished.");
$crashedJob->setModified(new \DateTime());
$entityManager->persist($crashedJob);
$entityManager->flush();
$freedJobs++;
}
}
if (count($runningJobs) - $freedJobs >= self::LIMIT_JOB_EXECUTION_COUNT) {
return new Response("The maximum number of jobs is already running.");
}
$nextJob = $entityManager->getRepository(Job::class)->findOneBy([
"status" => "queued"
], [
"created" => "ASC"
]);
if (!$nextJob) {
return new Response("Nothing to do.");
}
if ($nextJob->getType() == "details") {
$response = $this->forward('App\Controller\SnapshotController::createDetails', ["job_id" => $nextJob->getId(),"job_token" => $this->getParameter("job_token")]);
} else { //if ($nextJob->getType() == "overall") {
$response = $this->forward('App\Controller\SnapshotController::createOverall', ["job_id" => $nextJob->getId(),"job_token" => $this->getParameter("job_token")]);
}
if ($response->getContent() == "failed") {
// var_dump($response);
} else {
}
if(!isset($jobResponse)) { //In case of triggered by cronjob
die();
} else {
// if we get here there are maybe further jobs to be processed, so let's trigger them
JobController::triggerProcessingJobs($kernel);
return new Response("Processed job id " . $nextJob->getId() . ", result: " . $response->getContent());
}
}
}