src/EventSubscriber/DoctrineSubscriber.php line 164

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\Controller\Api\DocumentManagerController;
  4. use App\Entity\InvoicePaymentStatus;
  5. use App\Entity\InvoicePositions;
  6. use App\Entity\Invoices;
  7. use App\Entity\Partner;
  8. use App\Entity\PartnerCertificateDocuments;
  9. use App\Entity\ProjectOrders;
  10. use App\Entity\ProjectOrderTaskFulfillmentWorkDocs;
  11. use App\Entity\Projects;
  12. use App\Entity\ProjectStakeholders;
  13. use App\Entity\ProjectStatus;
  14. use App\Entity\StockItems;
  15. use App\Entity\Stocks;
  16. use App\Entity\Vendor;
  17. use App\Entity\WorkerActivities;
  18. use App\Entity\WorkerCertificateDocuments;
  19. use App\Entity\Worker;
  20. use App\Entity\WorkerInProject;
  21. use App\Enum\PaymentStatusEnum;
  22. use App\Service\Calculators\ProjectOrderCalculator;
  23. use Doctrine\Common\Collections\ArrayCollection;
  24. use Doctrine\Common\Collections\Criteria;
  25. use Doctrine\ORM\EntityManagerInterface;
  26. use Doctrine\ORM\Event\OnFlushEventArgs;
  27. use Doctrine\ORM\Event\PostFlushEventArgs;
  28. use Doctrine\ORM\Event\PrePersistEventArgs;
  29. use Doctrine\ORM\Event\PreRemoveEventArgs;
  30. use Doctrine\ORM\PersistentCollection;
  31. use Doctrine\Persistence\Event\LifecycleEventArgs;
  32. use Doctrine\Persistence\Event\LifecycleEventArgs as LifecycleEventArgsAlias;
  33. use Doctrine\ORM\Events;
  34. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  35. use App\Entity\InvoiceType;
  36. use Doctrine\Persistence\ManagerRegistry;
  37. use Doctrine\ORM\UnitOfWork;
  38. use Symfony\Contracts\Translation\TranslatorInterface;
  39. /**
  40.  * Doctrine during load jobs
  41. */
  42. class DoctrineSubscriber implements EventSubscriberInterface
  43. {
  44.     private $serviceArgs;
  45.     private ManagerRegistry $managerRegistry;
  46.     private TranslatorInterface $translator;
  47.     private ProjectOrderCalculator $projectOrderCalculator;
  48.     /**
  49.      * @deprecated
  50.     */
  51.     private PaymentStatusEnum $paymentStatusEnum;
  52.     public function __construct(
  53.         $args,
  54.         ManagerRegistry $managerRegistry,
  55.         TranslatorInterface $translator,
  56.         ProjectOrderCalculator $projectOrderCalculator,
  57.         PaymentStatusEnum $paymentStatusEnum
  58.     )
  59.     {
  60.         $this->serviceArgs = $args;
  61.         $this->managerRegistry = $managerRegistry;
  62.         $this->translator = $translator;
  63.         $this->projectOrderCalculator = $projectOrderCalculator;
  64.         $this->paymentStatusEnum = $paymentStatusEnum;
  65.     }
  66.     // Listen to postLoad event
  67.     public static function getSubscribedEvents()
  68.     {
  69.         /**
  70.          * Metrics hesaplama onFlush
  71.          * Delete öncesi kontrol preRemove
  72.          * DB sonrası işlem (log / event) postFlush
  73.          *
  74.          *  Metrics için önerilmez
  75.          *  preUpdate|postUpdate|postLoad
  76.         */
  77.         return array(
  78.             /**
  79.              * Entity DB’den yüklendikten hemen sonra
  80.             */
  81.             Events::postLoad,
  82.             #Events::postUpdate,
  83.             /**
  84.              * 📍 Ne zaman?
  85.              * persist() çağrıldıktan sonra
  86.              * flush() öncesi
  87.              * Sadece NEW entity için
  88.              * 📍 Ne için?
  89.              * Default value set
  90.              * createdAt / UUID
  91.             */
  92.             Events::prePersist,
  93.             /**
  94.              * 📍 Ne zaman?
  95.              * UPDATE edilecek entity için
  96.              * flush() öncesi
  97.              * 📍 Özel durum:
  98.              * ChangeSet zaten hesaplanmış
  99.              * Değişiklik yaptıysan:
  100.             */
  101.             #Events::preUpdate,
  102.             /**
  103.              * 📍 Ne zaman?
  104.              * remove() çağrıldıktan sonra
  105.              * flush() öncesi
  106.              * 📍 Ne için?
  107.              * Delete öncesi validation
  108.              * Loglama
  109.              * 🚫 Yapma: başka entity update
  110.             */
  111.             Events::preRemove,
  112.             /**
  113.              * 📍 Ne zaman?
  114.              * Doctrine ChangeSet’i hesapladıktan sonra
  115.              * SQL yazılmadan hemen önce
  116.              * 📍 Ne için? 🔥🔥🔥
  117.              * Başka entity’leri manuel olarak update etmek
  118.              * $uow->recomputeSingleEntityChangeSet()
  119.             */
  120.             Events::onFlush,
  121.             /**
  122.              * 📍 Ne zaman?
  123.              * Tüm SQL’ler çalıştıktan sonra
  124.              * Transaction commit edilmeden hemen önce
  125.              * 📍 Ne için?
  126.              * Side effects
  127.              * Queue / mail / log
  128.              * Flush çağırmak SERBEST (dikkatli)
  129.              * 🚫 Yapma: entity state değiştirme (sonsuz loop riski)
  130.             */
  131.             Events::postFlush
  132.         );
  133.     }
  134.     // Call on Update
  135.     public function postFlush(PostFlushEventArgs $args){
  136.     }
  137.     public function preRemove(PreRemoveEventArgs $args){
  138.         /*$manager = $args->getObjectManager();
  139.         $uow = $manager->getUnitOfWork();
  140.         #dd($uow->getScheduledEntityDeletions());
  141.         $entity = $args->getObject();
  142.         #dd($entity);
  143.         // Update Metrics
  144.         if( $entity instanceof ProjectOrderUndertakings){
  145.             $calculated = $this->projectOrderCalculator
  146.                 ->calculateOrderPayments($entity->getProjectOrder());
  147.             #dd($calculated);
  148.             $entity->getProjectOrder()->getProjectOrderMetrics()->setTotalUndertakings($calculated['undertakingTotal'] - $entity->getPrice());
  149.         }*/
  150.     }
  151.     // Call on (New & Update & Remove)
  152.     public function onFlush(OnFlushEventArgs $args)
  153.     {
  154.         /***
  155.          * $args->getObjectManager() → EntityManager döner
  156.          * ❌ Flush edilen entity dönmez
  157.          * ❌ instanceof ProjectOrderUndertakings burada anlamsız
  158.          * Yani şunlar artık BOŞ:
  159.          * getScheduledEntityInsertions()
  160.          * getScheduledEntityUpdates()
  161.          * getScheduledEntityDeletions()
  162.          */
  163.         $manager = $args->getObjectManager();
  164.         $uow = $manager->getUnitOfWork();
  165.         /**
  166.          * @deprecated
  167.         */
  168.         $deleted = array_values($uow->getScheduledEntityDeletions());
  169.         /**
  170.          * ornek entityRemove($entity) busekilde setEntity(null) oldugundan delete yapilmis entity degeri gorunmuyot yani deleted ici bos o yüzden preRemove kullan
  171.         */
  172.         /**
  173.          * $entities = [...$uow->getScheduledEntityUpdates(), ...$uow->getScheduledEntityInsertions()  ];
  174.          * // Trigger's
  175.          * foreach ($entities as $entity) {
  176.          *      if ($entity instanceof ProjectOrderUndertakings) {
  177.          *          * $trigger = new ProjectOrderMetricsTrigger($this->projectOrderCalculator);
  178.          *          * // $trigger->projectOrderUndertakingChange($uow, $manager, $entity, $deleted);
  179.          *          * // Only Remove is Manually With OnRemove in owned Service !!
  180.          *          * // ⚠️Don't use this success over inline ProjectOrderService
  181.          *          * $trigger->projectOrderUndertakingChange($uow, $manager, $entity, []);
  182.          * }
  183.          * }
  184.         */
  185.     }
  186.     // Call on (New)
  187.     public function prePersist( PrePersistEventArgs $args)
  188.     {
  189.         $entity = $args->getObject();
  190.         if( $entity instanceof ProjectOrderTaskFulfillmentWorkDocs ){
  191.             $entity->setFile($this->serviceArgs['dirs']['taskFulfillmentWorkDocsReadDir'] . DIRECTORY_SEPARATOR . $entity->getName() );
  192.         }
  193.     }
  194.     // Set the services.yaml param on the entity
  195.     public function postLoad( LifecycleEventArgs $args)
  196.     {
  197.         $entity = $args->getObject();
  198.         if ($entity instanceof PartnerCertificateDocuments ) {
  199.             $entity->setHydrateDocumentsData([
  200.                 "id" => $entity->getId(),
  201.                 "file_name" => $entity->getName(),
  202.                 "file_real_path" => $this->serviceArgs['dirs']['partnerCertFetchDir'] . DIRECTORY_SEPARATOR . $entity->getName(),
  203.                 "extension" =>  DocumentManagerController::getFileExtension( $entity->getName() ),
  204.                 "type" =>  DocumentManagerController::getFileFullType( DocumentManagerController::getFileExtension( $entity->getName() ) ),
  205.                 // Access from frontend
  206.                 "remove_path" => "/api/partner/remove-partner-certificate-history-document/" . $entity->getPartnerCertificateHistory()->getPartnerCertificate()->getPartner()->getSlug() . "/" . DocumentManagerController::Encrypt($entity->getId()),
  207.                 // Access just backend
  208.                 "remove_link" => $this->serviceArgs['dirs']['partnerCertManageDir'] . DIRECTORY_SEPARATOR . $entity->getName()
  209.             ]);
  210.         }
  211.         if ( $entity instanceof WorkerCertificateDocuments ) {
  212.             $entity->setHydrateDocumentsData([
  213.                 "id" => $entity->getId(),
  214.                 "file_name" => $entity->getName(),
  215.                 "file_real_path" => $this->serviceArgs['dirs']['workerCertFetchDir'] . DIRECTORY_SEPARATOR . $entity->getName(),
  216.                 "extension" =>  DocumentManagerController::getFileExtension( $entity->getName() ),
  217.                 "type" =>  DocumentManagerController::getFileFullType( DocumentManagerController::getFileExtension( $entity->getName() )),
  218.                 // Access from frontend
  219.                 "remove_path" => "/api/worker/remove-worker-certificate-history-document/" . $entity->getWorkerCertificateHistory()->getWorkerCertificate()->getWorker()->getSlug() . "/" . DocumentManagerController::Encrypt($entity->getId()),
  220.                 // Access just backend
  221.                 "remove_link" =>$this->serviceArgs['dirs']['workerCertManageDir'] . DIRECTORY_SEPARATOR . $entity->getName()
  222.             ]);
  223.         }
  224.         if ( $entity instanceof Worker ) {
  225.             #dd(12);
  226.             // Image Full Path
  227.             // $entity->setProfileImageFullPath( $this->serviceArgs['dirs']['workerImagesFetchDir'] . DIRECTORY_SEPARATOR . $entity->getProfileImage() );
  228. //            // SECTION “idempotent” Behevior
  229. //            $profileImage = $entity->getProfileImage();
  230. //            if ($profileImage && !str_starts_with($profileImage, $this->serviceArgs['dirs']['workerImagesFetchDir'])) {
  231. //                $entity->setProfileImage($this->serviceArgs['dirs']['workerImagesFetchDir'] . DIRECTORY_SEPARATOR . $profileImage);
  232. //            }
  233.             /**
  234.              * SECTION Worker için sadece runtime path ekle
  235.              * // README
  236.              * 1️⃣ Doctrine UnitOfWork “dirty check” yapıyor
  237.              * postLoad sırasında sen Worker entity’sinde setProfileImage() cagirdim.
  238.              * Doctrine bunu entity’nin değiştiği olarak algılar ve next flush’ta DB’ye yazılacak olarak işaretledi eget bu entity guncellemsini istemesem bile sirf bu yüzden worker guncellencek .
  239.              * postLoad içinde set yaptığımda, eğer o entity UnitOfWork tarafından izleniyorsa ve flush çağrılırsa, Doctrine bunu kaydediyor benide öldürüyor
  240.              * Gunvenli yol olarak runtime Path eklemeyi yaptim
  241.              * */
  242.             $this->setWorkerImagePathRuntime($entity);
  243.             // $entity->setProfileImage( $this->serviceArgs['dirs']['workerImagesFetchDir'] . DIRECTORY_SEPARATOR . $entity->getProfileImage() );
  244.             #dd($args->getObjectManager()->getMetadataFactory());
  245.             // Latest Activity
  246.             $criteria = Criteria::create()->where(Criteria::expr()->eq('exit_at', null));
  247.             // $criteria->orderBy(['created_at' => Criteria::DESC])->setMaxResults(1);
  248.             $criteria->orderBy(['id' => Criteria::DESC])->setMaxResults(1);
  249.             $latestActivity = $entity->getWorkerActivities()->matching($criteria)->first();
  250.             $latestActivity = $latestActivity instanceof WorkerActivities ? $latestActivity : null;
  251.             // Status
  252.             $status = false;
  253.             if ($latestActivity !== null) {
  254.                 $today = new \DateTimeImmutable('now');
  255.                 $exitAt = $latestActivity->getExitAt();
  256.                 if ($exitAt) {
  257.                     // Eğer çıkış zamanı gelecekteyse aktif kabul et
  258.                     $status = $today < $exitAt;
  259.                 } else {
  260.                     // Çıkış zamanı yoksa hâlâ aktif
  261.                     $status = true;
  262.                 }
  263.                 $latestActivity->setActivityStatus($status);
  264.                 if($exitAt){
  265.                     dd($exitAt, $today->diff($exitAt)->days, $status);
  266.                 }
  267.                 // Hesapladığın durumu activity'e ata
  268.             }
  269.             $entity
  270.                 ->setFullName( $entity->getName() . ' ' . $entity->getSurname() )
  271.                 ->setLatestActivity($latestActivity);
  272.         }
  273.         if ( $entity instanceof Partner ) {
  274.             // ⚠️ Yalnız mapped OLMAYAN logo_image_full_path türetilir; mapped company_logo'ya DOKUNULMAZ.
  275.             // postLoad'da değiştirilen mapped alan Doctrine'de dirty sayılır ve uygulamadaki HERHANGİ bir
  276.             // flush'ta prefix'li hâliyle DB'ye geri yazılır → her yüklemede bir prefix daha →
  277.             // "1406 Data too long for column 'company_logo'". Repair: sql/partner-company-logo-repair.sql
  278.             $logoFile = basename((string) $entity->getCompanyLogo());
  279.             $entity->setLogoImageFullPath(
  280.                 $logoFile !== '' && pathinfo($logoFile, PATHINFO_EXTENSION) !== ''
  281.                     ? $this->serviceArgs['dirs']['partnerImagesFetchDir'] . DIRECTORY_SEPARATOR . $logoFile
  282.                     : null
  283.             );
  284.         }
  285.         if ( $entity instanceof Vendor ) {
  286.             $entity->setVendorLogo( $this->serviceArgs['dirs']['vendorLogosDirectory'] . DIRECTORY_SEPARATOR . $entity->getVendorLogo() );
  287.         }
  288.         if( $entity instanceof ProjectOrderTaskFulfillmentWorkDocs ){
  289.             dump($this->serviceArgs['dirs']['taskFulfillmentWorkDocsReadDir'] . DIRECTORY_SEPARATOR . $entity->getName());
  290.             $entity->setFile($this->serviceArgs['dirs']['taskFulfillmentWorkDocsReadDir'] . DIRECTORY_SEPARATOR . $entity->getName() );
  291.         }
  292.         if ( $entity instanceof WorkerInProject || $entity instanceof ProjectStakeholders  || $entity instanceof Projects ) {
  293.             # 1,completed,success
  294.             # 2,in process,warning
  295.             $completed      = false;
  296.             $processId      = 2;
  297.             $message        = $this->translator->trans('in process');
  298.             $projectName    = '';
  299.             $alerted   = ''; // Bitis zamani belli henuz zamani gelmemis 
  300.             if( $entity instanceof Projects ){
  301.                 // Core First of all
  302.                 $project        = $entity;
  303.                 $projectEnded   = $project->getEndAt() && ($project->getEndAt() < new \DateTimeImmutable('now'));
  304.                 $projectName    = $project->getName();
  305.                 // $alerted        = $project->getEndAt() && ($project->getEndAt() > new \DateTimeImmutable('now'));
  306.                 $alerted        = !is_null($project->getEndAt()) ? (new \DateTimeImmutable('now'))->diff($project->getEndAt())->days : 'Infinity';
  307.                 if( $projectEnded ){
  308.                     $params = "completed";
  309.                     $completed = true;
  310.                 }
  311.             }
  312.             elseif( $entity instanceof ProjectStakeholders ){
  313.                 // Core First of all
  314.                 $project        = $entity->getProject();
  315.                 $projectEnded   = $project->getEndAt() && ($project->getEndAt() < new \DateTimeImmutable('now'));
  316.                 $projectName    = $project->getName();
  317.                 // $alerted        = !is_null($project->getEndAt()) && ($project->getEndAt() > new \DateTimeImmutable('now'));
  318.                 $alerted        = !is_null($project->getEndAt()) ? (new \DateTimeImmutable('now'))->diff($project->getEndAt())->days : 'Infinity';
  319.                 if($projectEnded){
  320.                     $completed = true;
  321.                     $message = $this->translator->trans('completed');
  322.                     $processId = 1;
  323.                 } else {
  324.                     // 2. Stakeholder
  325.                     $project        = $entity;
  326.                     $projectEnded   = $project->getEndAt() && ($project->getEndAt() < new \DateTimeImmutable('now'));
  327.                     // $alerted        = !is_null($project->getEndAt()) && ($project->getEndAt() > new \DateTimeImmutable('now'));
  328.                     $alerted        = !is_null($project->getEndAt()) ? (new \DateTimeImmutable('now'))->diff($project->getEndAt())->days : 'Infinity';
  329.                     if($projectEnded){
  330.                         $completed = true;
  331.                         $message = $this->translator->trans('completed');
  332.                         $processId = 1;
  333.                     }
  334.                 }
  335.             }
  336.             elseif( $entity instanceof WorkerInProject ){
  337.                 // Core First of all
  338.                 $project        = $entity->getProject()->getProject();
  339.                 $alerted        = 'Infinity';
  340.                 $projectEnded   = false;
  341.                 if(!is_null($project->getEndAt())){
  342.                     $diff           = (new \DateTimeImmutable('now'))->diff($project->getEndAt());
  343.                     $days           = $diff->days;
  344.                     $invert         = $diff->invert;
  345.                     $projectEnded   = $project->getEndAt() < new \DateTimeImmutable('now');
  346.                     $alerted        = $invert ? "-$days" : $days;
  347.                 }
  348.                 $projectName    = $project->getName();
  349.                 if($projectEnded){
  350.                     $completed = true;
  351.                     $message = $this->translator->trans('completed');
  352.                     $processId = 1;
  353.                 } else {
  354.                     // 2. Stakeholder
  355.                     $project        = $entity->getProject();
  356.                     $alerted        = 'Infinity';
  357.                     $projectEnded   = false;
  358.                     if(!is_null($project->getEndAt())){
  359.                         $diff           = (new \DateTimeImmutable('now'))->diff($project->getEndAt());
  360.                         $days           = $diff->days;
  361.                         $invert         = $diff->invert;
  362.                         $projectEnded   = $project->getEndAt() < new \DateTimeImmutable('now');
  363.                         $alerted        = $invert ? "-$days" : $days;
  364.                     }
  365.                     $projectName    = $project->getProject()->getName();
  366.                     if($projectEnded){
  367.                         $completed = true;
  368.                         $message = $this->translator->trans('completed');
  369.                         $processId = 1;
  370.                     } else {
  371.                         $project        = $entity;
  372.                         $alerted        = 'Infinity';
  373.                         $projectEnded   = false;
  374.                         if(!is_null($project->getEndAt())){
  375.                             $diff           = (new \DateTimeImmutable('now'))->diff($project->getEndAt());
  376.                             $days           = $diff->days;
  377.                             $invert         = $diff->invert;
  378.                             $projectEnded   = $project->getEndAt() < new \DateTimeImmutable('now');
  379.                             $alerted        = $invert ? "-$days" : $days;
  380.                         }
  381.                         $projectName    = $project->getProject()->getProject()->getName();
  382.                         if($projectEnded){
  383.                             $completed = true;
  384.                             $message = $this->translator->trans('completed');
  385.                             $processId = 1;
  386.                         }
  387.                     }
  388.                 }
  389.                 // TODO bu kisim gecici worker_in_project deki project prop u stakeholder_project prop u ile degisecek
  390.                 $entity->setStakeholderProject($entity->getProject());
  391.             }
  392.             try{
  393.                 #dump($message);
  394.                 /**@var $found ProjectStatus*/
  395.                 $found = $args->getObjectManager()->getRepository(ProjectStatus::class)->find($processId); // ->findOneBy(["name"=>$message]);
  396.                if(!is_null($found)){
  397.                     $entity->setStatus([
  398.                         "project_name" => $projectName,
  399.                         'name' => $message,
  400.                         'color' => $found->getColor(),
  401.                         'completed' => $completed,
  402.                         'alerted' => $alerted,
  403.                         'show_and_notify' => $alerted !== 'Infinity' && $alerted > 0 && $alerted < 10,
  404.                         'notify' => is_numeric($alerted) && $alerted > 0 ? $alerted . ' ' . $this->translator->trans('day(s) to end') : 'Infinity'
  405.                     ]);
  406.                }
  407.                else {
  408.                    dd("Null by ProjectStatus [$message]");
  409.                }
  410.             } catch (\Exception $exception ){
  411.                 throw new \Error("Error by ProjectStatus [$message]");
  412.             }
  413.         }
  414.         /**
  415.          * By Manage give an error 
  416.          * @deprecated */
  417.         if( $entity instanceof ProjectOrders ){
  418.         
  419.             return;
  420.             #dd($args->getObjectManager());
  421.             
  422.             $entityManager = $args->getObjectManager();
  423.             
  424.             $unitOfWork = $entityManager->getUnitOfWork();
  425.             $entityState = $unitOfWork->getEntityState($entity);
  426.             
  427.             if($entityState === UnitOfWork::STATE_NEW)  {
  428.                 // return;
  429.             }
  430.             #return;
  431.             try{
  432.                 if( is_null($entity->getInvoiceType())){
  433.                 
  434.                 
  435.                     #dd(12);
  436.                     $customer = $entity->getProject()->getCustomer();
  437.     
  438.     
  439.                     // Set Standard
  440.                     if(!$customer){
  441.                         $invoiceTypes = $args->getObjectManager()->getRepository(InvoiceType::class)->findOneBy(["name"=>"Standard"]);
  442.                         #dd($invoiceTypes);
  443.                         $entity->setInvoiceType($invoiceTypes);
  444.                         return;
  445.                     }
  446.     
  447.                     // Customer defined but invoice type not selected -> set Standard 
  448.                     if(is_null($customer->getInvoiceType())){
  449.                         $invoiceTypes = $args->getObjectManager()->getRepository(InvoiceType::class)->findOneBy(["name"=>"Standard"]);
  450.                         #dd($invoiceTypes);
  451.                         #$entity->setInvoiceType($invoiceTypes);
  452.                         return;
  453.                     }
  454.     
  455.                     // dd($customer->getInvoiceType());
  456.                     $entity->setInvoiceType($customer->getInvoiceType());
  457.                 }
  458.             } catch (\Exception $exception){
  459.                 
  460.             }
  461.         
  462.         }
  463.         if( $entity instanceof Stocks ){
  464.             $em = $args->getObjectManager();
  465.             $availableStockQuantity = $this->finalCalculatedStockQuantityFromTransactions($entity->getStockTransactions());
  466.             $entity->setAvailableStockQuantity( $availableStockQuantity );
  467.         }
  468.         if( $entity instanceof StockItems ){
  469.             $em = $args->getObjectManager();
  470.             $finalQuantities = $entity->getStocks()->map(function (/**@var Stocks $stock*/$stock) {
  471.                 return $this->finalCalculatedStockQuantityFromTransactions($stock->getStockTransactions());
  472.             });
  473.             $entity->setTotalAvailableQuantity(array_sum($finalQuantities->toArray()));
  474.         }
  475.         // TODO move this to static as Metric into Invoice Table
  476.         if($entity instanceof Invoices){
  477.             //            $calculated = $approved = $paid = [];
  478.             //
  479.             //            /**@var $invoicePosition InvoicePositions */
  480.             //            foreach ($entity->getInvoicePositions() as $invoicePosition) {
  481.             //                // This position absolute from Billing generated
  482.             //                $price  = $invoicePosition->getOrderBilling()->getPrice();
  483.             //                $unit   = $invoicePosition->getOrderBilling()->getQuantity();
  484.             //                // Required
  485.             //                $calculated[] = $price * $unit;
  486.             //                // Optional
  487.             //                if(!is_null($entity->getApprovedAt())) {
  488.             //                    $approved[] = $entity->getApprovedAmount();
  489.             //                }
  490.             //                // Optional
  491.             //                if(!is_null($entity->getPaidAt())){
  492.             //                    $paid[] = $invoicePosition->getPaidAmount();
  493.             //                }
  494.             //            }
  495.             //
  496.             //            // After each
  497.             //            $calculatedTotals   = array_sum($calculated);
  498.             //            $paidTotals         = array_sum($paid);
  499.             //            $approvedTotals     = array_sum($approved);
  500.             //            $paidStatus         = $this->paymentStatusEnum::PAYMENT_STATUS_UNPAID;
  501.             //            if($paidTotals > 0){
  502.             //                // TODO Paid
  503.             //                if( $paidTotals !== $calculatedTotals ){
  504.             //                    // TODO Partially Paid
  505.             //                    $paidStatus = $this->paymentStatusEnum::PAYMENT_STATUS_PARTIALLY_PAID;
  506.             //                } else {
  507.             //                    // TODO Fully Paid
  508.             //                    $paidStatus = $this->paymentStatusEnum::PAYMENT_STATUS_PAID;
  509.             //                }
  510.             //            }
  511.             //
  512.             //            $entity->setPaidStatus([
  513.             //                "calculated" => $calculatedTotals, "paid" => $paidTotals, "status" => $paidStatus
  514.             //            ]);
  515.         }
  516.         // Update Virtual Column
  517.         if($entity instanceof Invoices){
  518.             if(!is_null($entity->getPaidAt())){
  519.                 $entity->setWeekOfYear($entity->getPaidAt()->format('o-W'));
  520.             }
  521.         }
  522.     }
  523.     private function finalCalculatedStockQuantityFromTransactions(PersistentCollection $transactions): float {
  524.         $finalQuantity = 0;
  525.         foreach ( $transactions as $transaction) {
  526.             $qty = $transaction->getQuantity();
  527.             if ($transaction->getTransactionType() === 'in') {
  528.                 $finalQuantity += $qty;
  529.             } elseif ($transaction->getTransactionType() === 'out') {
  530.                 $finalQuantity -= $qty;
  531.             }
  532.         }
  533.         return $finalQuantity;
  534.     }
  535.     private function setWorkerImagePathRuntime(Worker $worker): void
  536.     {
  537.         $profileImage = $worker->getProfileImage();
  538.         if (!$profileImage) {
  539.             return;
  540.         }
  541.         // 1️⃣ Eğer zaten path eklenmişse tekrar ekleme
  542.         $fetchDir = rtrim($this->serviceArgs['dirs']['workerImagesFetchDir'], DIRECTORY_SEPARATOR);
  543.         if (!str_starts_with($profileImage, $fetchDir)) {
  544.             // 2️⃣ Runtime olarak path’i set et
  545.             // Reflection ile Doctrine dirty check’i bypass et
  546.             $reflection = new \ReflectionProperty(Worker::class, 'profile_image');
  547.             $reflection->setAccessible(true);
  548.             $reflection->setValue($worker, $fetchDir . DIRECTORY_SEPARATOR . $profileImage);
  549. //            $reflectionFull = new \ReflectionProperty(Worker::class, 'profileImageFullPath');
  550. //            $reflectionFull->setAccessible(true);
  551. //            $reflectionFull->setValue($worker, $fetchDir . DIRECTORY_SEPARATOR . $profileImage);
  552.         }
  553.     }
  554. }