src/Entity/ProjectOrderTasks.php line 16

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Repository\ProjectOrderTasksRepository;
  4. use Doctrine\Common\Collections\ArrayCollection;
  5. use Doctrine\Common\Collections\Collection;
  6. use Doctrine\Common\Collections\Selectable;
  7. use Doctrine\ORM\Mapping as ORM;
  8. use Symfony\Component\Serializer\Annotation\Groups;
  9. use Symfony\Component\Serializer\Annotation\SerializedName;
  10. /**
  11.  * @ORM\Entity(repositoryClass=ProjectOrderTasksRepository::class)
  12.  */
  13. class ProjectOrderTasks
  14. {
  15.     /**
  16.      * @ORM\Id
  17.      * @ORM\GeneratedValue
  18.      * @ORM\Column(type="integer")
  19.      * @Groups({
  20.      *     "project_order_task@base",
  21.      *     "project_order_task@core"
  22.      * })
  23.      */
  24.     private $id;
  25.     /**
  26.      * @ORM\Column(type="integer")
  27.      * @Groups({
  28.      *      "project_order_task@base",
  29.      *      "project_order_task@core"
  30.      *  })
  31.      */
  32.     private $task_index;
  33.     /**
  34.      * @ORM\Column(type="string", length=255)
  35.      * @Groups({
  36.      *      "project_order_task@base",
  37.      *      "project_order_task@core"
  38.      *  })
  39.      */
  40.     private $title;
  41.     /**
  42.      * @ORM\Column(type="text", nullable=true)
  43.      * @Groups({
  44.      *      "project_order_task@base",
  45.      *      "project_order_task@core"
  46.      *  })
  47.      */
  48.     private $description;
  49.     /**
  50.      * @ORM\Column(type="float")
  51.      * @Groups({
  52.      *      "project_order_task@core"
  53.      *  })
  54.      */
  55.     private $progress_weight;
  56.     /**
  57.      * @ORM\Column(type="integer", nullable=true)
  58.      * @Groups({
  59.      *      "project_order_task@core"
  60.      *  })
  61.      */
  62.     private $duration_days;
  63.     /**
  64.      * @deprecated never use this controlling over job
  65.      * @ORM\Column(type="date_immutable", nullable=true, options={"comment": "deprecated -> Redundant field; value is dynamically calculated based on completed job weights."})
  66.      * @Groups({
  67.      *      "project_order_task@core"
  68.      *  })
  69.      */
  70.     private $started_at;
  71.     /**
  72.      * @deprecated never use this controlling over job
  73.      * @ORM\Column(type="date_immutable", nullable=true, options={"comment": "deprecated -> Redundant field; value is dynamically calculated based on completed job weights."})
  74.      * @Groups({
  75.      *      "project_order_task@core"
  76.      *  })
  77.      */
  78.     private $completed_at;
  79.     /**
  80.      * @ORM\ManyToOne(targetEntity=ProjectOrders::class, inversedBy="project_order_tasks")
  81.      * @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
  82.      * @Groups({
  83.      *        "project_order_task@order"
  84.      *    })
  85.      */
  86.     private $project_order;
  87.     /**
  88.      * @ORM\OneToMany(targetEntity=ProjectOrderTaskJobs::class, mappedBy="order_task", cascade={"persist"}, orphanRemoval=true )
  89.      * @Groups({
  90.      *       "project_order_task@task_jobs"
  91.      *   })
  92.      */
  93.     private $task_jobs;
  94.     /**
  95.      * Uretim asamasi (Produktionsstatus) — TODO #109.
  96.      *
  97.      * ILISKI, string KOPYA DEGIL (kullanici karari 2026-08-19). Once kod
  98.      * string olarak tutuluyordu; gerekcesi "asama silinirse gecmis bozulmasin"
  99.      * idi. SILME TAMAMEN IPTAL EDILINCE o senaryo ortadan kalkti, geriye
  100.      * yalnizca iki alani (kod + etiket iliskisi) senkron tutma zorunlulugu
  101.      * kaldi — yani tutarsizlik riski. Tek alan, tek gercek.
  102.      *
  103.      * 🪤 `onDelete` VERILMEDI, BILEREK: Doctrine varsayilani RESTRICT'tir, yani
  104.      * bir task'a bagli asamayi DB'nin KENDISI sildirmez. "Bagli asama silinmez,
  105.      * yalnizca `active=false` yapilir" kurali boylece yorumda degil SEMADA
  106.      * yasiyor; kimse kodu okumadan da ihlal edemez.
  107.      *
  108.      * Etiket bedavaya gelir: serializer iliskiyi gezip `code` + `label`'i ayni
  109.      * payload'a koyar (`task_fulfillment_stage@base`), FE ikinci istek atmaz.
  110.      *
  111.      * @ORM\ManyToOne(targetEntity=TaskFulfillmentStage::class)
  112.      * @ORM\JoinColumn(nullable=true)
  113.      * @Groups({
  114.      *     "project_order_task@base",
  115.      *     "project_order_task@core"
  116.      * })
  117.      */
  118.     private $fulfillment_stage;
  119.     /**
  120.      * @ORM\OneToOne(targetEntity=ProjectOrderTaskFulfillment::class, mappedBy="task", cascade={"persist", "remove"})
  121.      * @Groups({
  122.      *     "project_order_task@fulfillment"
  123.      * })
  124.      */
  125.     private $task_fulfillment;
  126.     /**
  127.      * @ORM\ManyToOne(targetEntity=WorkerActivities::class, inversedBy="project_order_tasks")
  128.      * @Groups({
  129.      *         "project_order_task@responsible_person"
  130.      *  })
  131.      */
  132.     private $responsible_person;
  133.     public function __construct()
  134.     {
  135.         $this->task_jobs = new ArrayCollection();
  136.     }
  137.     public function getId(): ?int
  138.     {
  139.         return $this->id;
  140.     }
  141.     public function getTaskIndex(): ?int
  142.     {
  143.         return $this->task_index;
  144.     }
  145.     public function setTaskIndex(int $task_index): self
  146.     {
  147.         $this->task_index = $task_index;
  148.         return $this;
  149.     }
  150.     public function getTitle(): ?string
  151.     {
  152.         return $this->title;
  153.     }
  154.     public function setTitle(string $title): self
  155.     {
  156.         $this->title = $title;
  157.         return $this;
  158.     }
  159.     public function getDescription(): ?string
  160.     {
  161.         return $this->description;
  162.     }
  163.     public function setDescription(?string $description): self
  164.     {
  165.         $this->description = $description;
  166.         return $this;
  167.     }
  168.     public function getProgressWeight(): ?float
  169.     {
  170.         return $this->progress_weight;
  171.     }
  172.     public function setProgressWeight(float $progress_weight): self
  173.     {
  174.         $this->progress_weight = $progress_weight;
  175.         return $this;
  176.     }
  177.     public function getDurationDays(): ?int
  178.     {
  179.         return $this->duration_days;
  180.     }
  181.     public function setDurationDays(?int $duration_days): self
  182.     {
  183.         $this->duration_days = $duration_days;
  184.         return $this;
  185.     }
  186.     public function getStartedAt(): ?\DateTimeImmutable
  187.     {
  188.         return $this->started_at;
  189.     }
  190.     public function setStartedAt(?\DateTimeImmutable $started_at): self
  191.     {
  192.         $this->started_at = $started_at;
  193.         return $this;
  194.     }
  195.     public function getCompletedAt(): ?\DateTimeImmutable
  196.     {
  197.         return $this->completed_at;
  198.     }
  199.     public function setCompletedAt(?\DateTimeImmutable $completed_at): self
  200.     {
  201.         $this->completed_at = $completed_at;
  202.         return $this;
  203.     }
  204.     public function getProjectOrder(): ?ProjectOrders
  205.     {
  206.         return $this->project_order;
  207.     }
  208.     public function setProjectOrder(?ProjectOrders $project_order): self
  209.     {
  210.         $this->project_order = $project_order;
  211.         return $this;
  212.     }
  213.     /**
  214.      * TaskExecution servisi (ağ içi `internal/executions`) + owner FE için sanal alan — task'ın
  215.      * bağlı olduğu siparişin sipariş numarası. DB kolonu DEĞİL, `project_order->order_nr`'dan türer.
  216.      * `project_order` nullable=false olduğu için normalde her zaman dolu döner; savunma amaçlı
  217.      * null-safe. T55: çok-kelimeli sanal getter camelCase serialize olur → `@SerializedName` ZORUNLU.
  218.      *
  219.      * @Groups({
  220.      *     "project_order_task@core"
  221.      * })
  222.      * @SerializedName("order_nr")
  223.      */
  224.     public function getOrderNr(): ?string
  225.     {
  226.         return $this->project_order ? $this->project_order->getOrderNr() : null;
  227.     }
  228.     /**
  229.      * /**
  230.      *  ⚠️ IMPORTANT – Collection index / Serializer issue
  231.      *
  232.      *  Problem:
  233.      *  --------
  234.      *  ProjectOrderTaskJobs koleksiyonu (Doctrine ArrayCollection) üzerinde
  235.      *  BİRDEN FAZLA remove işlemi yapıldığında, Doctrine elemanları silerken
  236.      *  array indexlerini yeniden sıralamaz.
  237.      *
  238.      *  Örnek:
  239.      *    [0 => JobA, 1 => JobB, 2 => JobC]
  240.      *    JobB silinirse →
  241.      *    [0 => JobA, 2 => JobC]   // index boşluğu oluşur ❌
  242.      *
  243.      *  Symfony Serializer bu yapıyı:
  244.      *    - indexler ardışık değilse → associative object `{}` olarak,
  245.      *    - indexler 0..n ise → JSON array `[]` olarak serialize eder.
  246.      *
  247.      *  Bu nedenle çoklu silme sonrası FE tarafında:
  248.      *    task_jobs: { "1": {...} }
  249.      *  gibi HATALI bir çıktı oluşur.
  250.      *
  251.      *  Çözüm:
  252.      *  ------
  253.      *  Serializer'a gönderilmeden önce collection MUTLAKA reindex edilmelidir.
  254.      *  Bu da getter seviyesinde yapılır:
  255.      *
  256.      *    return new ArrayCollection(array_values($this->task_jobs->toArray()));
  257.      *
  258.      *  Böylece:
  259.      *    - Doctrine iç davranışı bozulmaz
  260.      *    - Serializer her zaman `[...]` JSON array üretir
  261.      *    - FE tarafında index problemi yaşanmaz
  262.      *
  263.      *  Not:
  264.      *  ----
  265.      *  Bu problem serializer flag'leri (preserve_empty_objects vb.)
  266.      *  ile çözülemez; sorun serialization değil, collection index yapısıdır.
  267.      * /
  268.      * @return Collection<int, ProjectOrderTaskJobs>&Selectable
  269.      */
  270.     public function getTaskJobs(): Collection
  271.     {
  272.         return new ArrayCollection(array_values($this->task_jobs->toArray()));
  273.     }
  274.     public function addProjectOrderTaskJob(ProjectOrderTaskJobs $projectOrderTaskJob): self
  275.     {
  276.         if (!$this->task_jobs->contains($projectOrderTaskJob)) {
  277.             $this->task_jobs[] = $projectOrderTaskJob;
  278.             $projectOrderTaskJob->setOrderTask($this);
  279.         }
  280.         return $this;
  281.     }
  282.     /**
  283.      * @see ProjectOrderTasks::getTaskJobs()
  284.     */
  285.     public function removeProjectOrderTaskJob(ProjectOrderTaskJobs $projectOrderTaskJob): self
  286.     {
  287.         if ($this->task_jobs->removeElement($projectOrderTaskJob)) {
  288.             // set the owning side to null (unless already changed)
  289.             if ($projectOrderTaskJob->getOrderTask() === $this) {
  290.                 $projectOrderTaskJob->setOrderTask(null);
  291.             }
  292.         }
  293.         return $this;
  294.     }
  295.     public function getFulfillmentStage(): ?TaskFulfillmentStage
  296.     {
  297.         return $this->fulfillment_stage;
  298.     }
  299.     public function setFulfillmentStage(?TaskFulfillmentStage $fulfillment_stage): self
  300.     {
  301.         $this->fulfillment_stage = $fulfillment_stage;
  302.         return $this;
  303.     }
  304.     public function getTaskFulfillment(): ?ProjectOrderTaskFulfillment
  305.     {
  306.         return $this->task_fulfillment;
  307.     }
  308.     public function setTaskFulfillment(?ProjectOrderTaskFulfillment $task_fulfillment): self
  309.     {
  310.         // set the owning side of the relation if necessary
  311.         if ($task_fulfillment->getTask() !== $this) {
  312.             $task_fulfillment->setTask($this);
  313.         }
  314.         $this->task_fulfillment = $task_fulfillment;
  315.         return $this;
  316.     }
  317.     public function getResponsiblePerson(): ?WorkerActivities
  318.     {
  319.         return $this->responsible_person;
  320.     }
  321.     public function setResponsiblePerson(?WorkerActivities $responsible_person): self
  322.     {
  323.         $this->responsible_person = $responsible_person;
  324.         return $this;
  325.     }
  326. }