src/Controller/Api/v3/ProjectController.php line 126

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Api\v3;
  3. use App\Controller\Api\Service\CustomerService;
  4. use App\Controller\Api\Service\ProjectService;
  5. use App\Entity\Projects;
  6. use App\Entity\User;
  7. use App\Enum\SeverityInterface;
  8. use App\Service\SerializeService\BranchSerialize;
  9. use App\Service\SerializeService\CustomerSerialize;
  10. use App\Service\SerializeService\ProjectSerialize;
  11. use App\Service\SerializeService\ReferenceSerialize;
  12. use App\Service\StreamingService;
  13. use Doctrine\Common\Collections\ArrayCollection;
  14. use Doctrine\ORM\EntityManagerInterface;
  15. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  16. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpFoundation\Response;
  19. use Symfony\Component\HttpFoundation\StreamedResponse;
  20. use Symfony\Component\Routing\Annotation\Route;
  21. use Symfony\Component\Serializer\SerializerInterface;
  22. use Symfony\Contracts\Translation\TranslatorInterface;
  23. /**
  24.  * @Route("/api/v3/project")
  25. */
  26. class ProjectController extends AbstractController
  27. {
  28.     /**
  29.      * ✅
  30.      * @Route("/streamed-fetch/{project_id}", name="api_v3_project_fetch_streamed", defaults={"project_id": null})
  31.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  32.      */
  33.     public function streamedFetch(?Projects $projectTranslatorInterface $translatorProjectService $projectServiceProjectSerialize $projectSerializeBranchSerialize $branchSerializeCustomerSerialize $customerSerializeReferenceSerialize $referenceSerialize): StreamedResponse
  34.     {
  35.         $streaming = new StreamingService([
  36.             "localText" => ["completed" => $translator->trans('Completed')]
  37.         ]);
  38.         $streaming->appendStream(
  39.             "branches",
  40.             $translator->trans('Loading project data'),
  41.             // 🔥Branches fetch $ serialize
  42.             fn() => $branchSerialize->branches()
  43.         );
  44.         $streaming->appendStream(
  45.             "customers",
  46.             $translator->trans('Loading customers data'),
  47.             // 🔥Customer fetch $ serialize
  48.             fn() => $customerSerialize->serializeCore()
  49.         );
  50.         $streaming->appendStream(
  51.             "countries",
  52.             $translator->trans('Loading countries data'),
  53.             // 🔥 Countries fetch $ serialize
  54.             fn() => $referenceSerialize->serializedCountries()
  55.         );
  56.         $streaming->appendStream(
  57.             "certificates",
  58.             $translator->trans('Loading certificates data'),
  59.             // 🔥 Certificates fetch $ serialize
  60.             fn() => $referenceSerialize->serializedCertificates()
  61.         );
  62.         $streaming->appendStream(
  63.             "qualifications",
  64.             $translator->trans('Loading Qualifications data'),
  65.             // 🔥 Qualifications fetch $ serialize
  66.             fn() => $referenceSerialize->serializedQualifications()
  67.         );
  68.         $streaming->appendStream(
  69.             "invoice_types",
  70.             $translator->trans('Loading Invoice type'),
  71.             // 🔥 InvoiceTypes fetch $ serialize
  72.             fn() => $referenceSerialize->serializedInvoiceTypes()
  73.         );
  74.         if($project){
  75.             $streaming->appendStream(
  76.                 "project",
  77.                 $translator->trans('Loading Project'),
  78.                 fn() => $projectSerialize->setEntity($project)->serializeFull()
  79.             );
  80.         }
  81.         return $streaming->flush();
  82.     }
  83.     /**
  84.      * ✅
  85.      * @Route("/projects", name="api_v3_projects")
  86.      * @param EntityManagerInterface $manager
  87.      * @param SerializerInterface $serializer
  88.      * @param ProjectSerialize $projectSerialize
  89.      * @return Response
  90.      * Partner Analyse for projects
  91.      */
  92.     public function allProjectsEntityManagerInterface $managerSerializerInterface $serializerProjectSerialize $projectSerialize ): Response
  93.     {
  94.         $projects = new ArrayCollection($manager->getRepository(Projects::class)->findAll());
  95.         $projects $projectSerialize->setEntity($projects)->serializeWithRequirements();
  96.         return $this->json([
  97.             "projects" => $projects
  98.         ], Response::HTTP_OK, [], []);
  99.     }
  100.     /**
  101.      * ✅
  102.      * @Route("/projects/{classify}", name="api_v3_projects_classify")
  103.      * @param ProjectService $projectService
  104.      * @param ProjectSerialize $projectSerialize
  105.      * @param TranslatorInterface $translator
  106.      * @param Request $request
  107.      * @param string $classify
  108.      * @return Response
  109.      * Partner Analyse for projects
  110.      * @throws \Exception
  111.      */
  112.     public function fetchClassifyProjects(ProjectService $projectServiceProjectSerialize $projectSerializeTranslatorInterface $translatorRequest $requeststring $classify "active" ): Response
  113.     {
  114.         $projects $projectService->fetchProjects($classify);
  115.         if($projectService->getException()){
  116.             return $this->json([
  117.                 "message" => $projectService->getException()->getMessage(),
  118.                 "severity" => SeverityInterface::WARNING
  119.             ], $projectService->getException()->getCode());
  120.         }
  121.         $serializedProjects $projectSerialize->setEntity($projects)->serializeForMainList();
  122.         return $this->json([
  123.             "projects" => $serializedProjects,
  124.             $classify => $serializedProjects,
  125.             "message" => $translator->trans('Projects fetched successfully'),
  126.             "severity" => SeverityInterface::SUCCESS
  127.         ], Response::HTTP_OK);
  128.     }
  129.     /**
  130.      *
  131.      * @Route("/fetch/{project_id}", name="api_v3_project_fetch", methods={"POST"}, requirements={"project_id": "\d+"})
  132.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  133.      * @param int|null $project_id
  134.      * @throws \Exception
  135.      */
  136.     public function fetch( ?Projects $projectProjectSerialize $projectSerialize,
  137.                                   CustomerSerialize $customerSerialize,
  138.                                   ReferenceSerialize $referenceSerialize,
  139.                                   CustomerService $customerServiceTranslatorInterface $translatorint $project_id ): Response
  140.     {
  141.         if( $project_id && !$project ){
  142.             return $this->json([
  143.                 "message" => $translator->trans('Project not found'),
  144.                 "severity" => SeverityInterface::ERROR
  145.             ], Response::HTTP_NOT_FOUND);
  146.         }
  147.         $serializedProject $projectSerialize->setEntity($project)->serializeFull();
  148.         return $this->json([
  149.             "message" => $translator->trans('Loaded project successfully'),
  150.             "severity" => SeverityInterface::SUCCESS,
  151.             "project" => $serializedProject,
  152.         ], Response::HTTP_OK );
  153.     }
  154.     /**
  155.      * ✅✅
  156.      * @Route("/assign-branches/{project_id}", name="api_v3_project_assign_branches_to_project", defaults={"project_id": null})
  157.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  158.      * @param int|null $project_id
  159.      * @return Response
  160.      * Partner Analyse for projects
  161.      * @throws \Exception
  162.      */
  163.     public function assignBranches( ?Projects $projectTranslatorInterface $translatorProjectService $projectServiceRequest $requestProjectSerialize $projectSerializeint $project_id null ): Response
  164.     {
  165.         if($project_id && !$project){
  166.             return $this->json([
  167.                 "message" => $translator->trans('Project not found'),
  168.                 "severity" => SeverityInterface::ERROR
  169.             ]);
  170.         }
  171.         $params $request->request;
  172.         #dd($params);
  173.         $project $projectService->setEntity($project$project_id)->setParams($params)->joinBranches();
  174.         if($projectService->getException()){
  175.             return $this->json([
  176.                 "message" => $projectService->getException()->getMessage(),
  177.                 "severity" => SeverityInterface::ERROR
  178.             ]);
  179.         }
  180.         $serializedProject $projectSerialize->setEntity($project)->serializeFull();
  181.         return $this->json([
  182.             "message" => $translator->trans('Joined branches to project successfully'),
  183.             "severity" => SeverityInterface::SUCCESS,
  184.             "project" => $serializedProject
  185.         ], Response::HTTP_OK, [], []);
  186.     }
  187.     /**
  188.      * ✅
  189.      * @Route("/upsert/{project_id}", name="api_v3_project_upsert", defaults={"project_id": null})
  190.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  191.      * @param int|null $project_id
  192.      * @return Response
  193.      * Partner Analyse for projects
  194.      * @throws \Exception
  195.      */
  196.     public function upsert( ?Projects $projectTranslatorInterface $translatorProjectService $projectServiceRequest $requestProjectSerialize $projectSerializeint $project_id null ): Response
  197.     {
  198.         if($project_id && !$project){
  199.             return $this->json([
  200.                 "message" => $translator->trans('Project not found'),
  201.                 "severity" => SeverityInterface::ERROR
  202.             ]);
  203.         }
  204.         $project $projectService->setEntity($project$project_id)->setParams($request->request)->upsert();
  205.         if($projectService->getException()){
  206.             return $this->json([
  207.                 "message" => $projectService->getException()->getMessage(),
  208.                 "severity" => SeverityInterface::WARNING
  209.             ]);
  210.         }
  211.         $serializedProject $projectSerialize->setEntity($project)->serializeFull();
  212.         #dd($serializedProject);
  213.         return $this->json([
  214.             "message" => $translator->trans('Project upsert successfully'),
  215.             "severity" => SeverityInterface::SUCCESS,
  216.             "project" => $serializedProject
  217.         ], Response::HTTP_OK, [], []);
  218.     }
  219.     /**
  220.      * TODO README !!!
  221.      * TODO Sync (Progress) Su an icin (ProjectOrderService) icinde bagimsiz yonetiliyor!, onysaki projece calculator Servise var onda Isleri parcalara böl ve o servisi kullan !!!
  222.      * ✅
  223.      * @Route("/sync-progress/{project_id}", name="api_project_sync_progress", methods={"POST"})
  224.      * @ParamConverter("project", options={"mapping":{"project_id":"id"}})
  225.      */
  226.     public function syncProgress(?Projects $projectProjectService $projectServiceProjectSerialize $projectSerializeTranslatorInterface $translator, ?int $project_id null ): Response
  227.     {
  228.         try{
  229.             $project $projectService->setEntity($project$project_id)->syncMetricProgress();
  230.             $serializedProject $projectSerialize->setEntity($project)->serializeFull();
  231.             return $this->json([
  232.                 'project' => $serializedProject,
  233.                 "message"=>$translator->trans('Progress sync successfully'),
  234.                 "severity" => SeverityInterface::SUCCESS
  235.             ], Response::HTTP_OK );
  236.         } catch (\Exception $exception){
  237.             return $this->json([
  238.                 'message'=>$exception->getMessage(),
  239.                 "severity" => SeverityInterface::ERROR
  240.             ], Response::HTTP_UNAUTHORIZED );
  241.         }
  242.     }
  243.     /**
  244.      *
  245.      * @Route("/overview/{project_id}", name="api_v3_project_overview", methods={"POST"}, requirements={"project_id": "\d+"})
  246.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  247.      * @param int|null $project_id
  248.      * @throws \Exception
  249.      */
  250.     public function overview( ?Projects $projectProjectSerialize $projectSerialize,
  251.                            TranslatorInterface $translatorint $project_id ): Response
  252.     {
  253.         if( $project_id && !$project ){
  254.             return $this->json([
  255.                 "message" => $translator->trans('Project not found'),
  256.                 "severity" => SeverityInterface::ERROR
  257.             ], Response::HTTP_NOT_FOUND);
  258.         }
  259.         $serializedProject $projectSerialize->setEntity($project)->serializeFull();
  260.         $serializedMetrics $projectSerialize->setEntity($project)->serializeOverview();
  261.         return $this->json([
  262.             "message" => $translator->trans('Loaded project successfully'),
  263.             "severity" => SeverityInterface::SUCCESS,
  264.             "project" => $serializedProject,
  265.             "metrics" => $serializedMetrics
  266.         ], Response::HTTP_OK );
  267.     }
  268.     /**
  269.      * @Route("/re-classify/{classify}", name="app_api_project_reclassify", methods={"POST"})
  270.      * @ParamConverter("project", options={"mapping": {"project_id": "id"}})
  271.      * @throws \Exception
  272.      */
  273.     public function reClassify ProjectService $projectServiceProjectSerialize $projectSerializeRequest $requestTranslatorInterface $translatorstring $classify "all"): Response {
  274.         $projectService
  275.             ->setParams($request->request)
  276.             ->reClassify($classify);
  277.         if(!is_null($projectService->getException())){
  278.             return $this->json([
  279.                 "message" => $projectService->getException()->getMessage(),
  280.                 "severity" => SeverityInterface::ERROR
  281.             ], Response::HTTP_NOT_ACCEPTABLE);
  282.         }
  283.         $sourceClassify     $request->request->get('source');
  284.         $targetClassify     $classify;
  285.         $projectsTarget     $projectService->fetchProjects($targetClassify);
  286.         $projectsCurrent    $projectService->fetchProjects($sourceClassify);
  287.         if($projectService->getException()){
  288.             return $this->json([
  289.                 "message" => $projectService->getException()->getMessage(),
  290.                 "severity" => SeverityInterface::WARNING
  291.             ], $projectService->getException()->getCode());
  292.         }
  293.         // Moved To
  294.         $serTarget  $projectSerialize->setEntity($projectsTarget)->serializeForMainList();
  295.         // Leaved from
  296.         $serCurr    $projectSerialize->setEntity($projectsCurrent)->serializeForMainList();
  297.         return $this->json([
  298.             // Current Classify
  299.             "source" => ["key" => $sourceClassify"value" => $serCurr],
  300.             "target" => ["key" => $classify"value" => $serTarget],
  301.             "message" => $translator->trans('Projects classified successfully'),
  302.             "severity" => SeverityInterface::SUCCESS
  303.         ], Response::HTTP_OK);
  304.     }
  305.     /**
  306.      * @Route("/labor-details/{project_id}", name="api_v3_project_labor_details", methods={"POST"}, requirements={"project_id": "\d+"})
  307.      * @ParamConverter("project", options={"mapping": {"project_id": "id"}})
  308.      */
  309.     public function laborDetails(?Projects $projectProjectService $projectServiceRequest $request): Response
  310.     {
  311.         $laborDetails $projectService
  312.             ->setEntity($project)
  313.             ->setParams($request->request)
  314.             ->laborDetails();
  315.         if ($projectService->getException()) {
  316.             return $this->json([
  317.                 'message'  => $projectService->getException()->getMessage(),
  318.                 'severity' => SeverityInterface::WARNING,
  319.             ], Response::HTTP_BAD_REQUEST);
  320.         }
  321.         return $this->json($laborDetailsResponse::HTTP_OK);
  322.     }
  323.     /**
  324.      * Tüm projelerin metriklerini SSE (Server-Sent Events) stream ile hesaplar.
  325.      * Her proje işlendikçe progress event'i gönderilir.
  326.      * Abort veya hata durumunda Doctrine transaction rollback yapılır (DB'ye yazılmaz).
  327.      *
  328.      * Nginx: proxy_buffering off  veya  X-Accel-Buffering: no header gereklidir.
  329.      *
  330.      * @Route("/sync-all-metrics", name="api_v3_project_sync_all_metrics", methods={"POST"})
  331.      */
  332.     public function syncAllMetrics(ProjectService $projectServiceRequest $request): StreamedResponse
  333.     {
  334.         $syncToken = (string) $request->request->get('syncToken'uniqid('sync_'true));
  335.         return new StreamedResponse(function() use ($projectService$syncToken) {
  336.             ignore_user_abort(false);
  337.             $emit = function(array $event) {
  338.                 echo 'data: ' json_encode($event) . "\n\n";
  339.                 if (ob_get_level() > 0) {
  340.                     ob_flush();
  341.                 }
  342.                 flush();
  343.             };
  344.             $projectService->syncAllMetricsStreamed($syncToken$emit);
  345.         }, Response::HTTP_OK, [
  346.             'Content-Type'      => 'text/event-stream',
  347.             'Cache-Control'     => 'no-cache, no-store',
  348.             'X-Accel-Buffering' => 'no',
  349.             'Connection'        => 'keep-alive',
  350.         ]);
  351.     }
  352.     /**
  353.      * Tek bir projenin metriklerini SSE stream ile hesaplar.
  354.      * syncAllMetricsStreamed'in aynı altyapısını kullanır, sadece o projeye filtreli.
  355.      *
  356.      * @Route("/sync-project-metrics/{project_id}", name="api_v3_project_sync_project_metrics", methods={"POST"}, requirements={"project_id": "\d+"})
  357.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  358.      */
  359.     public function syncProjectMetrics(?Projects $projectProjectService $projectServiceRequest $requestint $project_id): StreamedResponse
  360.     {
  361.         if (!$project) {
  362.             return new StreamedResponse(function() use ($project_id) {
  363.                 echo 'data: ' json_encode(['type' => 'error''message' => "Project #{$project_id} not found"]) . "\n\n";
  364.                 flush();
  365.             }, Response::HTTP_OK, [
  366.                 'Content-Type'      => 'text/event-stream',
  367.                 'Cache-Control'     => 'no-cache, no-store',
  368.                 'X-Accel-Buffering' => 'no',
  369.             ]);
  370.         }
  371.         $syncToken = (string) $request->request->get('syncToken'uniqid('sync_'true));
  372.         return new StreamedResponse(function() use ($projectService$syncToken$project) {
  373.             ignore_user_abort(false);
  374.             $emit = function(array $event) {
  375.                 echo 'data: ' json_encode($event) . "\n\n";
  376.                 if (ob_get_level() > 0) {
  377.                     ob_flush();
  378.                 }
  379.                 flush();
  380.             };
  381.             $projectService->syncAllMetricsStreamed($syncToken$emit$project->getId());
  382.         }, Response::HTTP_OK, [
  383.             'Content-Type'      => 'text/event-stream',
  384.             'Cache-Control'     => 'no-cache, no-store',
  385.             'X-Accel-Buffering' => 'no',
  386.             'Connection'        => 'keep-alive',
  387.         ]);
  388.     }
  389.     /**
  390.      * FE'nin abort sinyali gönderdiği endpoint.
  391.      * Temp dosyaya flag yazar; syncAllMetricsStreamed her iterasyonda bunu kontrol eder.
  392.      *
  393.      * @Route("/abort-sync", name="api_v3_project_abort_sync", methods={"POST"})
  394.      */
  395.     public function abortSync(ProjectService $projectServiceRequest $request): Response
  396.     {
  397.         $syncToken = (string) $request->request->get('syncToken''');
  398.         if (empty($syncToken)) {
  399.             return $this->json(['message' => 'syncToken required'], Response::HTTP_BAD_REQUEST);
  400.         }
  401.         $projectService->setAbortFlag($syncToken);
  402.         return $this->json(['message' => 'Abort signal received'], Response::HTTP_OK);
  403.     }
  404.     /**
  405.      * Bos projeyi siler (order / stakeholder / timesheet payment yoksa).
  406.      * Bos degilse bloklar.
  407.      *
  408.      * @Route("/delete/{project_id}", name="api_v3_project_delete", methods={"POST"}, requirements={"project_id": "\d+"})
  409.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  410.      */
  411.     public function delete(?Projects $projectProjectService $projectServiceTranslatorInterface $translatorRequest $request, ?int $project_id null): Response
  412.     {
  413.         if ($project_id && !$project) {
  414.             return $this->json([
  415.                 "message" => $translator->trans("Project not found!"),
  416.                 "severity" => SeverityInterface::ERROR
  417.             ], Response::HTTP_NOT_FOUND);
  418.         }
  419.         // Stakeholder/payment blogunu asmak icin force + override sifresi
  420.         $force filter_var($request->request->get('force'), FILTER_VALIDATE_BOOLEAN);
  421.         $password $request->request->get('password');
  422.         $removedId $projectService->setEntity($project)->cascadeDelete($force$password);
  423.         if ($projectService->getException()) {
  424.             $exception $projectService->getException();
  425.             return $this->json([
  426.                 "message" => $exception->getMessage(),
  427.                 "severity" => SeverityInterface::WARNING
  428.             ], $exception->getCode() === Response::HTTP_UNAUTHORIZED Response::HTTP_UNAUTHORIZED Response::HTTP_BAD_REQUEST);
  429.         }
  430.         return $this->json([
  431.             "message" => $translator->trans('Project deleted successfully'),
  432.             "severity" => SeverityInterface::SUCCESS,
  433.             "id" => $removedId,
  434.             "mode" => "delete"
  435.         ], Response::HTTP_OK);
  436.     }
  437.     /**
  438.      * Force-delete onizlemesi: projenin tum bagimlilik ozeti (aggregate).
  439.      *
  440.      * @Route("/dependency-summary/{project_id}", name="api_v3_project_dependency_summary", methods={"POST"}, requirements={"project_id": "\d+"})
  441.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  442.      */
  443.     public function dependencySummary(?Projects $projectProjectService $projectServiceTranslatorInterface $translator): Response
  444.     {
  445.         if (!$project) {
  446.             return $this->json([
  447.                 "message" => $translator->trans("Project not found!"),
  448.                 "severity" => SeverityInterface::ERROR
  449.             ], Response::HTTP_NOT_FOUND);
  450.         }
  451.         return $this->json([
  452.             "summary" => $projectService->setEntity($project)->dependencySummary()
  453.         ], Response::HTTP_OK);
  454.     }
  455.     /**
  456.      * Projeyi + TUM bagimliliklarini streamed (adim adim) force-delete eder. GERI DONUS YOK.
  457.      * EventSource yalnizca GET -> methods YAZILMAZ. Sifre query param (basit desen).
  458.      *
  459.      * @Route("/force-delete-streamed/{project_id}", name="api_v3_project_force_delete_streamed", requirements={"project_id": "\d+"})
  460.      * @ParamConverter("project", options={"mapping":{"project_id": "id"}})
  461.      */
  462.     public function forceDeleteStreamed(?Projects $projectProjectService $projectServiceRequest $request): StreamedResponse
  463.     {
  464.         $password $request->query->get('password');
  465.         return $projectService->setEntity($project)->forceDeleteStreamed($password);
  466.     }
  467. }