src/Services/Platform/DevisTools.php line 56

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/Services/Platform/DevisTools.php
  4. //----------------------------------------------------------------------
  5. namespace App\Services\Platform;
  6. use Doctrine\Common\Collections\ArrayCollection;
  7. use Doctrine\Persistence\ManagerRegistry;
  8. use Symfony\Component\Security\Core\Security;
  9. use Symfony\Contracts\Translation\TranslatorInterface;
  10. use App\Services\Common\TextTools;
  11. use App\Services\Location\AddressTools;
  12. use App\Services\LogTools;
  13. use App\Services\Product\ProductTools;
  14. use App\Entity\Access;
  15. use App\Entity\Client\Client;
  16. use App\Entity\Common\Attachment;
  17. use App\Entity\Ikea\Product as IkeaProduct;
  18. use App\Entity\Ikea\TemplateProduct as IkeaTemplateProduct;
  19. use App\Entity\Ikea\Template as IkeaTemplate;
  20. use App\Entity\Ikea\TemplateData;
  21. use App\DTO\Ikea\ViewMethod2026Data;
  22. use App\Entity\Planning\Task;
  23. use App\Entity\Platform\Devis\Devis;
  24. use App\Entity\Platform\Devis\DevisProduct;
  25. use App\Entity\Platform\Devis\DevisStatus;
  26. use App\Entity\Platform\Invoice\Invoice;
  27. use App\Entity\Platform\Invoice\InvoiceProduct;
  28. use App\Entity\Planning\TaskStatus;
  29. use App\Entity\Platform\Society;
  30. use App\Entity\Product\Config as ProductConfig;
  31. use App\Entity\Product\Product;
  32. use App\Entity\Product\Unit;
  33. use App\Entity\ProjectManager\ProjectNotebook;
  34. use App\Services\Ding\AdvancedNotificationTools;
  35. use App\Services\Product\ProductConfigTools;
  36. class DevisTools
  37. {
  38.     public function __construct(
  39.         ManagerRegistry $doctrine, TranslatorInterface $translator,
  40.         TextTools $textTools, AddressTools $addressTools,
  41.         ProductTools $productTools, Security $security, LogTools $logTools, AdvancedNotificationTools $advancedNotificationTools,
  42.         ProductConfigTools $productConfigTools
  43.     )
  44.     {
  45.         $this->em = $doctrine->getManager();
  46.         $this->translator = $translator;
  47.         $this->logTools = $logTools;
  48.         $this->textTools = $textTools;
  49.         $this->addressTools = $addressTools;
  50.         $this->productTools = $productTools;
  51.         $this->advancedNotificationTools = $advancedNotificationTools;
  52.         $this->productConfigTools = $productConfigTools;
  53.         $this->security = $security;
  54.     }
  55.     // Plan.io Task #4624
  56.     public function belongsToProjectManager(Devis $devis)
  57.     {
  58.         if ($devis->getProjectNotebook() !== null)
  59.         {
  60.             return true;
  61.         }
  62.         $notebook = $this->em->getRepository(ProjectNotebook::class)
  63.             ->findOneByPreparatoryWorkDevis($devis);
  64.         if ($notebook !== null)
  65.         {
  66.             return true;
  67.         }
  68.         $notebook = $this->em->getRepository(ProjectNotebook::class)
  69.             ->findOneByFinishingWorkDevis($devis);
  70.         if ($notebook !== null)
  71.         {
  72.             return true;
  73.         }
  74.         // ALl hope is lost
  75.         return false;
  76.     }
  77.     // Plan.io Task #4593
  78.     public function getLinearMeterData(Devis $devis)
  79.     {
  80.         $templateData = $devis->getIkeaTemplateData();
  81.         if ($templateData === null)
  82.         {
  83.             return null;
  84.         }
  85.         $methodVersion = intval($devis->getMethodVersion());
  86.         // Decide if 2025 or 2026
  87.         if ($methodVersion == 2026)
  88.         {                        
  89.             return $templateData->getViewMethod2026Dto();            
  90.         }
  91.         
  92.         if ($methodVersion == 2025)
  93.         {            
  94.             return $templateData->getViewMethod2025Dto();
  95.         }
  96.         return null;
  97.     }
  98.     
  99.     // Plan.io Task #4383
  100.     // We want a list of IkeaServiceOrders which meet all the following criteria
  101.     //        - ServiceOrder is attached to Devis->Mission
  102.     //        - ServiceOrder->orderNumber = Devis->ikeaOrderNumber
  103.     //        - ServiceOrder->isAfterSalesService
  104.     //            (ie. ServiceOrder->serviceCode in ServiceOrder::SAV_SERVICE_CODES)
  105.     public function getIkeaSavServiceOrders(Devis $devis)
  106.     {
  107.         if (!$this->devisIsIkeaSAV($devis))
  108.         {
  109.             // Not a Ikea SAV Devis => Die.Hard
  110.             return new ArrayCollection();
  111.         }
  112.         // At this point we know that
  113.         //    - the Devis has a non empty ikeaOrderNumber
  114.         //     - the Devis has a not null mission
  115.         //     - the Mission has at least one Ikea ServiceOrder with a SAV ServiceCode
  116.         $devisIkeaOrderNumber = $devis->getIkeaOrderNumber();
  117.         $mission = $devis->getMission();
  118.         $ikeaServiceOrders = new ArrayCollection();
  119.         foreach ($mission->getIkeaServiceOrders() as $so)
  120.         {
  121.             if ($devis->getIkeaOrderNumber() != $so->getOrderNumber())
  122.             {
  123.                 // Not the same Order Number => Skip
  124.                 continue;
  125.             }
  126.             if (!$so->isAfterSalesService())
  127.             {
  128.                 // Not a "SAV" Service Code => Skip
  129.                 continue;
  130.             }
  131.             // All looks good
  132.             if ($ikeaServiceOrders->contains($so))
  133.             {
  134.                 // Already added => Skip
  135.                 continue;
  136.             }
  137.             $ikeaServiceOrders[] = $so;
  138.         }
  139.         return $ikeaServiceOrders;
  140.     }
  141.     // Plan.io Task #4383
  142.     // "Ikea SAV" Devis = a "timming" Devis linked to an "SAV" Ikea ServiceOrder
  143.     // Check Devis not annulled | refused
  144.     public function devisIsIkeaSAV(Devis $devis)
  145.     {
  146.         if ($devis->isAnnulled() || $devis->isRefused())
  147.         {
  148.             return false;
  149.         }
  150.         if (empty($devis->getIkeaOrderNumber()))
  151.         {
  152.             // Not a Ikea Devis => Die.Hard
  153.             return false;
  154.         }
  155.         if (!$devis->hasHourProduct())
  156.         {
  157.             // Not a "timming" Devis => Die.Hard
  158.             return false;
  159.         }
  160.         $mission = $devis->getMission();
  161.         if ($mission === null)
  162.         {
  163.             // This should not happen
  164.             return false;
  165.         }
  166.         if (empty($mission->getIkeaServiceOrders()))
  167.         {
  168.             // Not a Ikea Mission => Die.Hard
  169.             // However since the Devis has an ikeaOrderNumber this should not happen
  170.             return false;
  171.         }
  172.         foreach ($mission->getIkeaServiceOrders() as $ikeaServiceOrder)
  173.         {
  174.             if ($devis->getIkeaOrderNumber() != $ikeaServiceOrder->getOrderNumber())
  175.             {
  176.                 // Not the same Order Number => Skip
  177.                 continue;
  178.             }
  179.             if (!$ikeaServiceOrder->isAfterSalesService())
  180.             {
  181.                 // Not a "SAV" Service Code => Skip
  182.                 continue;
  183.             }
  184.             // If we are here it means we have
  185.             // a "timming" Devis linked to an "SAV" Ikea ServiceOrder
  186.             // => All looks good
  187.             return true;
  188.         }
  189.         // All hope is lost
  190.         return false;
  191.     }
  192.     // Plan.io Task #3793
  193.     public function sign(Devis $devis, Access $coAuthor)
  194.     {
  195.         $societyGroup = $devis->getSocietyGroup();
  196.         if ($societyGroup === null)
  197.         {
  198.             // This should not happen
  199.             $this->logTools->errorlog("SocietyGroup is null for Devis ".$devis->displayForLog());
  200.             return false;
  201.         }
  202.         if ($devis->hasSignature() === false)
  203.         {
  204.             // This should not happen
  205.             return false;
  206.         }
  207.         // Update Signature date and co-author
  208.         $devis->setSignatureDate(new \DateTime());
  209.         $devis->setSignatureCoAuthor($coAuthor);
  210.         // Change status to accepted
  211.         $acceptedStatus = $this->em->getRepository(DevisStatus::class)
  212.             ->findOneBy(array(
  213.                 'societyGroup'        =>    $societyGroup,
  214.                 'accepted'            =>    1,
  215.             ));
  216.         if ($acceptedStatus === null)
  217.         {
  218.             // This should not happen
  219.             $this->logTools->errorlog("Accepted DevisStatus is null for societyGroup ".$societyGroup->displayForLog());
  220.             return false;
  221.         }
  222.         $devis->setStatus($acceptedStatus);
  223.         // All looks good
  224.         return true;
  225.     }
  226.     // Plan.io Task #2849
  227.     public function detectReInvoicingProblem($devis)
  228.     {
  229.         // The problem only arises when trying to re-invoice a devis with installments
  230.         if ($devis->getInstallments()->count() < 1)
  231.         {
  232.             return null;
  233.         }
  234.         // No invoice, no problem
  235.         // if ($devis->getInvoices()->count() < 1)
  236.         // Plan.io Task #4326 : Only consider actual invoices, and exclude drafts
  237.         if ($devis->getInvoicesOnly()->count() < 1)
  238.         {
  239.             return null;
  240.         }
  241.         // If we are here it means that the devis has installments and invoices
  242.         // Just check to see if any of them match
  243.         foreach ($devis->getInstallments() as $installment)
  244.         {
  245.             if ($installment->getLinkInvoice() !== null)
  246.             {
  247.                 $invoice = $installment->getLinkInvoice()->getInvoice();
  248.                 if ($invoice !== null)
  249.                 {
  250.                     // This installment is linked to an invoice
  251.                     // Does the invoice have a credit invoice ?
  252.                     // Plan.io Task #4326 : Do not take drafts into account
  253.                     $credit = $this->em->getRepository(Invoice::class)->findOneBy(array(
  254.                         'creditInvoice'        =>    $invoice,
  255.                         'draft'                =>    0,
  256.                     ));
  257.                     if ($credit !== null)
  258.                     {
  259.                         return array(
  260.                             'devis'            =>    $devis,
  261.                             'invoice'        =>    $invoice,
  262.                             'credit'        =>    $credit,
  263.                         );
  264.                     }
  265.                 }
  266.             }
  267.         }
  268.         return null;
  269.     }
  270.     /**
  271.      * Plan.io Task #3867 - Retrieves and calculates profitability data
  272.      *
  273.      * @param Devis $devis
  274.      * @return array {
  275.      *     profitability: int,
  276.      *     profitabilityPercent: int,
  277.      *     totalCostProducts: int,
  278.      *     interventionCost: int,
  279.      *     totalCost: int,
  280.      * }
  281.      */
  282.     public function getDataTiming(Devis $devis)
  283.     {
  284.         $totalCostProducts = 0;
  285.         foreach ($devis->getProducts() as $product)
  286.         {
  287.             $totalCostProducts += $product->getRefCost() * $product->getQuantity();
  288.         }
  289.         $interventionCost = 0;
  290.         $society = $devis->getSociety();
  291.         if ($society !== null)
  292.         {
  293.             // Switch for % or fixe value
  294.             if ($society->getInterventionCostType() == Society::INT_COST_TYPE_PERCENT)
  295.             {
  296.                 $interventionCost = $devis->getTotalInternalHTAfterDiscount() * $society->getInterventionCostPercent() / 100;
  297.             }
  298.             else if ($society->getInterventionCostType() == Society::INT_COST_TYPE_FIXE)
  299.             {
  300.                 $interventionCost = $society->getInterventionCost();
  301.             }
  302.         }
  303.         $otherCostProvision = $devis->getOtherCostProvision();
  304.         $totalCost = $totalCostProducts + $interventionCost + $otherCostProvision;
  305.         $profitability = $devis->getTotalInternalHTAfterDiscount() - $totalCost;
  306.         if($devis->getTotalInternalHTAfterDiscount() != 0)
  307.         {
  308.             $profitabilityPercent = $profitability / $devis->getTotalInternalHTAfterDiscount() * 100;
  309.         }
  310.         else
  311.         {
  312.             $profitabilityPercent = 0;
  313.         }
  314.         return array(
  315.             "profitability"         => $profitability,
  316.             "profitabilityPercent"     => $profitabilityPercent,
  317.             "totalCostProducts"     => $totalCostProducts,
  318.             "interventionCost"         => $interventionCost,
  319.             "totalCost"             => $totalCost,
  320.         );
  321.     }
  322.     public function suggestEmitters($devis, $access, $mission)
  323.     {
  324.         $receiver = $devis->getReceiver();
  325.         if ($receiver === null)
  326.         {
  327.             return array();
  328.         }
  329.         $individual = $receiver->getIndividual();
  330.         if ($individual === null)
  331.         {
  332.             // Store, this one is simple
  333.             $devis->setEmitter($receiver);
  334.             return array($receiver);
  335.         }
  336.         $clientRepository = $this->em->getRepository(Client::class);
  337.         $emitterIds = array();
  338.         $emitters = new ArrayCollection();
  339.         // Lesson learned : Do not use ArrayCollection->contains on big collections
  340.         // ArrayCollection::contains() compares object identity (===), not database ID. 
  341.         // The same client loaded via different paths can be a different PHP instance, 
  342.         // so duplicates pile up and contains() does expensive linear scans on a growing collection.
  343.         
  344.         // Fetch store clients via DQL instead of traversing Access->Society->Store->Client
  345.         // (ArrayCollection::contains uses identity comparison and loads the full entity graph)
  346.         foreach ($clientRepository->getStoreClientsForAccessAndSociety($access, $mission->getSociety()) as $client)
  347.         {
  348.             $id = $client->getId();
  349.             if ($id !== null && !isset($emitterIds[$id]))
  350.             {
  351.                 $emitterIds[$id] = true;
  352.                 $emitters[] = $client;
  353.             }
  354.         }
  355.         // Also add the emitters of the individual
  356.         foreach ($clientRepository->getStoresForIndividual($receiver) as $client)
  357.         {
  358.             $id = $client->getId();
  359.             if ($id !== null && !isset($emitterIds[$id]))
  360.             {
  361.                 $emitterIds[$id] = true;
  362.                 $emitters[] = $client;
  363.             }
  364.         }
  365.         // Add the mission emitter
  366.         $missionEmitter = $mission->getEmitter();
  367.         if ($missionEmitter !== null)
  368.         {
  369.             // Make sure the select contains the good (default) emitter also
  370.             $missionEmitterId = $missionEmitter->getId();
  371.             if ($missionEmitterId !== null && !isset($emitterIds[$missionEmitterId]))
  372.             {
  373.                 $emitterIds[$missionEmitterId] = true;
  374.                 $emitters[] = $missionEmitter;
  375.             }
  376.             // Set emitter
  377.             $devis->setEmitter($missionEmitter);
  378.         }
  379.         else
  380.         {
  381.             // Mission has no emitters
  382.             // But does the access have any ? (this is like MissionController::addAction)
  383.             if (count($emitters))
  384.             {
  385.                 // Get the first one
  386.                 $emitter = $emitters[0];
  387.                 $devis->setEmitter($emitter);
  388.             }
  389.             else
  390.             {
  391.                 // All hope is lost, set emitter just like receiver
  392.                 $devis->setEmitter($receiver);
  393.             }
  394.         }
  395.         // Sort emitters
  396.         if (count($emitters))
  397.         {
  398.             $iterator = $emitters->getIterator();
  399.             $iterator->uasort(function ($first, $second) {
  400.                 return (int) $first->getName() < (int) $second->getName() ? 1 : -1;
  401.             });
  402.             $emitters = $iterator;
  403.         }
  404.         $emitters = new ArrayCollection(iterator_to_array($emitters));
  405.         return $emitters;
  406.     }
  407.     public function handleFiles($devis)
  408.     {
  409.         $attachmentRepository = $this->em->getRepository(Attachment::class);
  410.         $files = $devis->getFiles();
  411.         if ($files !== null)
  412.         {
  413.             $files = json_decode($files, true);
  414.             foreach ($files as $uuid)
  415.             {
  416.                 $attachment = $attachmentRepository->findOneByUuid($uuid);
  417.                 if ($attachment !== null)
  418.                     $devis->addAttachment($attachment);
  419.             }
  420.         }
  421.     }
  422.     public function devisToArray($devis)
  423.     {
  424.         $authorId = null;
  425.         $authorUsername = null;
  426.         $societyId = null;
  427.         $societyRef = null;
  428.         $societyName = null;
  429.         $societyGroupId = null;
  430.         $societyGroupRef = null;
  431.         $societyGroupName = null;
  432.         $receiverIsIndividual = 0;
  433.         $receiverId = null;
  434.         $receiverName = null;
  435.         $receiverLastname = null;
  436.         $receiverFirstname = null;
  437.         $receiverPhone = null;
  438.         $receiverEmail = null;
  439.         $receiverAddress = null;
  440.         $receiverComplement = null;
  441.         $receiverPostalCode = null;
  442.         $receiverTown = null;
  443.         $receiverCountry = null;
  444.         $emitterIsReceiver = 0;
  445.         $emitterId = null;
  446.         $emitterName = null;
  447.         $emitterEmail = null;
  448.         $emitterPhone = null;
  449.         $emitterAddress = null;
  450.         $emitterComplement = null;
  451.         $emitterPostalCode = null;
  452.         $emitterTown = null;
  453.         $emitterCountry = null;
  454.         $statusId = null;
  455.         $statusValue = null;
  456.         $templateId = null;
  457.         $templateRef = null;
  458.         $templateTitle = null;
  459.         $templateDescription = null;
  460.         $templateSocietyGroupRef = null;
  461.         $templateSocietyGroupName = null;
  462.         $interventionAddressAddress = null;
  463.         $interventionAddressComplement = null;
  464.         $interventionAddressPostalCode = null;
  465.         $interventionAddressTown = null;
  466.         $interventionAddressCountry = null;
  467.         $billingAddressAddress = null;
  468.         $billingAddressComplement = null;
  469.         $billingAddressPostalCode = null;
  470.         $billingAddressTown = null;
  471.         $billingAddressCountry = null;
  472.         $tva = 20.00;
  473.         $secondTva = null;
  474.         $thirdTva = null;
  475.         $products = array();
  476.         $nbAttachments = null;
  477.         $devisSimulation = 0;
  478.         if ($devis->getAuthor() !== null)
  479.         {
  480.             $authorId = $devis->getAuthor()->getId();
  481.             $authorUsername = $devis->getAuthor()->getUsername();
  482.         }
  483.         if ($devis->getSociety() !== null)
  484.         {
  485.             $societyId = $devis->getSociety()->getId();
  486.             $societyRef = $devis->getSociety()->getRef();
  487.             $societyName = $devis->getSociety()->getName();
  488.             if ($devis->getSociety()->getGroup() !== null)
  489.             {
  490.                 $societyGroupId = $devis->getSociety()->getGroup()->getId();
  491.                 $societyGroupRef = $devis->getSociety()->getGroup()->getRef();
  492.                 $societyGroupName = $devis->getSociety()->getGroup()->getName();
  493.             }
  494.         }
  495.         if ($devis->getReceiver() !== null)
  496.         {
  497.             $receiverId = $devis->getReceiver()->getId();
  498.             $receiverName = $devis->getReceiver()->getName();
  499.             $receiverPhone = $devis->getReceiver()->getPhone();
  500.             $receiverEmail = $devis->getReceiver()->getEmail();
  501.             if ($devis->getReceiver()->getAddress() !== null)
  502.             {
  503.                 $receiverAddress = $devis->getReceiver()->getAddress()->getAddress();
  504.                 $receiverPostalCode = $devis->getReceiver()->getAddress()->getPostalCode();
  505.                 $receiverTown = $devis->getReceiver()->getAddress()->getTown();
  506.                 $receiverComplement = $devis->getReceiver()->getAddress()->getComplement();
  507.                 $receiverCountry = $devis->getReceiver()->getAddress()->getCountry();
  508.             }
  509.             if ($devis->getReceiver()->getIndividual() !== null)
  510.             {
  511.                 $receiverIsIndividual = 1;
  512.                 $receiverLastname = $devis->getReceiver()->getIndividual()->getLastname();
  513.                 $receiverFirstname = $devis->getReceiver()->getIndividual()->getFirstname();
  514.             }
  515.         }
  516.         if ($devis->getEmitter() !== null)
  517.         {
  518.             $emitterId = $devis->getEmitter()->getId();
  519.             $emitterName = $devis->getEmitter()->getName();
  520.             $emitterEmail = $devis->getEmitter()->getEmail();
  521.             $emitterPhone = $devis->getEmitter()->getPhone();
  522.             if ($devis->getEmitter()->equals($devis->getReceiver()))
  523.             {
  524.                 $emitterIsReceiver = 1;
  525.             }
  526.             if ($devis->getEmitter()->getAddress() !== null)
  527.             {
  528.                 $emitterAddress = $devis->getEmitter()->getAddress()->getAddress();
  529.                 $emitterPostalCode = $devis->getEmitter()->getAddress()->getPostalCode();
  530.                 $emitterTown = $devis->getEmitter()->getAddress()->getTown();
  531.                 $emitterComplement = $devis->getEmitter()->getAddress()->getComplement();
  532.                 $emitterCountry = $devis->getEmitter()->getAddress()->getCountry();
  533.             }
  534.         }
  535.         if ($devis->getTemplate() !== null)
  536.         {
  537.             $templateId = $devis->getTemplate()->getId();
  538.             $templateRef = $devis->getTemplate()->getRef();
  539.             $templateTitle = $devis->getTemplate()->getTitle();
  540.             $templateDescription = $devis->getTemplate()->getDescription();
  541.             if ($devis->getTemplate()->getSocietyGroup() !== null)
  542.             {
  543.                 $templateSocietyGroupRef = $devis->getTemplate()->getSocietyGroup()->getRef();
  544.                 $templateSocietyGroupName = $devis->getTemplate()->getSocietyGroup()->getName();
  545.             }
  546.         }
  547.         if ($devis->getSimulation())
  548.         {
  549.             $devisSimulation = 1;
  550.         }
  551.         if ($devis->getTva() !== null)
  552.         {
  553.             $tva = $devis->getTva()->getValue();
  554.         }
  555.         if ($devis->getSecondTva() !== null)
  556.         {
  557.             $secondTva = $devis->getSecondTva()->getValue();
  558.         }
  559.         if ($devis->getThirdTva() !== null)
  560.         {
  561.             $thirdTva = $devis->getThirdTva()->getValue();
  562.         }
  563.         if ($devis->getInterventionAddress() !== null)
  564.         {
  565.             $interventionAddressAddress = $devis->getInterventionAddress()->getAddress();
  566.             $interventionAddressPostalCode = $devis->getInterventionAddress()->getPostalCode();
  567.             $interventionAddressTown = $devis->getInterventionAddress()->getTown();
  568.             $interventionAddressComplement = $devis->getInterventionAddress()->getComplement();
  569.             $interventionAddressCountry = $devis->getInterventionAddress()->getCountry();
  570.         }
  571.         if ($devis->getBillingAddress() !== null)
  572.         {
  573.             $billingAddressAddress = $devis->getBillingAddress()->getAddress();
  574.             $billingAddressPostalCode = $devis->getBillingAddress()->getPostalCode();
  575.             $billingAddressTown = $devis->getBillingAddress()->getTown();
  576.             $billingAddressComplement = $devis->getBillingAddress()->getComplement();
  577.             $billingAddressCountry = $devis->getBillingAddress()->getCountry();
  578.         }
  579.         foreach ($devis->getProducts() as $devisProduct)
  580.         {
  581.             // Start with the original product data
  582.             $product = $devisProduct->getOriginalProduct();
  583.             if ($product !== null)
  584.             {
  585.                 $originalProductData = $this->productTools->productToArray($product);
  586.                 $originalProductId = $product->getId();
  587.             }
  588.             else
  589.             {
  590.                 $originalProductId = null;
  591.                 $originalProductData = array();
  592.             }
  593.             // Add the devis_product data
  594.             $devisProductData = $this->productTools->productDevisToArray($devisProduct);
  595.             $products[] = array(
  596.                 "original_product_id"            =>    $originalProductId,
  597.                 "original_product_data"            =>    $originalProductData,
  598.                 "devis_product_data"            =>    $devisProductData,
  599.             );
  600.         }
  601.         $nbAttachments = $devis->getAttachments()->count();
  602.         return array(
  603.             "devis_id"                        =>    $devis->getId(),
  604.             "devis_ref"                        =>    $devis->getRef(),
  605.             "devis_simulation"                =>    $devisSimulation,
  606.             "author_id"                        =>    $authorId,
  607.             "author_username"                =>    $authorUsername,
  608.             "society_id"                    =>    $societyId,
  609.             "society_ref"                    =>    $societyRef,
  610.             "society_name"                    =>    $societyName,
  611.             "society_group_id"                =>    $societyGroupId,
  612.             "society_group_ref"                =>    $societyGroupRef,
  613.             "society_group_name"            =>    $societyGroupName,
  614.             "receiver_is_individual"        =>    $receiverIsIndividual,
  615.             "receiver_id"                    =>    $receiverId,
  616.             "receiver_name"                    =>    $receiverName,
  617.             "receiver_lastname"                =>    $receiverLastname,
  618.             "receiver_firstname"            =>    $receiverFirstname,
  619.             "receiver_phone"                =>    $receiverPhone,
  620.             "receiver_email"                =>    $receiverEmail,
  621.             "receiver_address"                =>    $receiverAddress,
  622.             "receiver_complement"            =>    $receiverComplement,
  623.             "receiver_postal_code"            =>    $receiverPostalCode,
  624.             "receiver_town"                    =>    $receiverTown,
  625.             "receiver_country"                =>    $receiverCountry,
  626.             "emitter_is_receiver"            =>    $emitterIsReceiver,
  627.             "emitter_id"                    =>    $emitterId,
  628.             "emitter_name"                    =>    $emitterName,
  629.             "emitter_email"                    =>    $emitterEmail,
  630.             "emitter_phone"                    =>    $emitterPhone,
  631.             "emitter_address"                =>    $emitterAddress,
  632.             "emitter_complement"            =>    $emitterComplement,
  633.             "emitter_postal_code"            =>    $emitterPostalCode,
  634.             "emitter_town"                    =>    $emitterTown,
  635.             "emitter_country"                =>    $emitterCountry,
  636.             "status_id"                        =>    $statusId,
  637.             "status_value"                    =>    $statusValue,
  638.             "nb_attachments"                =>    $nbAttachments,
  639.             "tva"                            =>    $tva,
  640.             "second_tva"                    =>    $this->textTools->encodeForJson($secondTva),
  641.             "third_tva"                        =>    $this->textTools->encodeForJson($thirdTva),
  642.             "devis_title"                                    =>    $devis->getTitle(),
  643.             "devis_info"                                    =>    $devis->getInfo(),
  644.             "devis_ref_order"                                =>    $devis->getRefOrder(),
  645.             "devis_mention_info"                            =>    $this->textTools->encodeForJson($devis->getMentionInfo()),
  646.             "devis_annulled_info"                            =>    $devis->getAnnulledInfo(),
  647.             "devis_percentage_invoicing"                    =>    $this->textTools->encodeForJson($devis->getPercentageInvoicing()),
  648.             "template_id"                                    =>    $templateId,
  649.             "template_ref"                                    =>    $templateRef,
  650.             "template_title"                                =>    $templateTitle,
  651.             "template_description"                            =>    $templateDescription,
  652.             "template_society_group_ref"                    =>    $templateSocietyGroupRef,
  653.             "template_society_group_name"                    =>    $templateSocietyGroupName,
  654.             "billing_address_address"                        =>    $billingAddressAddress,
  655.             "billing_address_complement"                    =>    $billingAddressComplement,
  656.             "billing_address_postal_code"                    =>    $billingAddressPostalCode,
  657.             "billing_address_town"                            =>    $billingAddressTown,
  658.             "billing_address_country"                        =>    $billingAddressCountry,
  659.             "intervention_address_address"                    =>    $interventionAddressAddress,
  660.             "intervention_address_complement"                =>    $interventionAddressComplement,
  661.             "intervention_address_postal_code"                =>    $interventionAddressPostalCode,
  662.             "intervention_address_town"                        =>    $interventionAddressTown,
  663.             "intervention_address_country"                    =>    $interventionAddressCountry,
  664.             "total_internal_ht"                                =>    $devis->getTotalInternalHT(),
  665.             "total_public_ht"                                =>    $devis->getTotalPublicHT(),
  666.             "total_internal_ht_after_discount"                =>    $devis->getTotalInternalHTAfterDiscount(),
  667.             "total_public_ht_after_discount"                =>    $devis->getTotalPublicHTAfterDiscount(),
  668.             "total_internal_ttc_after_discount"                =>    $devis->getTotalInternalTTCAfterDiscount(),
  669.             "total_public_ttc_after_discount"                =>    $devis->getTotalPublicTTCAfterDiscount(),
  670.             "total_internal_discount"                        =>    $devis->getTotalInternalDiscount(),
  671.             "total_public_discount"                            =>    $devis->getTotalPublicDiscount(),
  672.             "total_internal_discount_ttc"                    =>    $devis->getTotalInternalDiscountTTC(),
  673.             "total_public_discount_ttc"                        =>    $devis->getTotalPublicDiscountTTC(),
  674.             "total_internal_ttc"                            =>    $devis->getTotalInternalTTC(),
  675.             "total_public_ttc"                                =>    $devis->getTotalPublicTTC(),
  676.             "total_internal_tva"                            =>    $devis->getTotalInternalTVA(),
  677.             "total_public_tva"                                =>    $devis->getTotalPublicTVA(),
  678.             "total_internal_tva_after_discount"                =>    $devis->getTotalInternalTVAAfterDiscount(),
  679.             "total_public_tva_after_discount"                =>    $devis->getTotalPublicTVAAfterDiscount(),
  680.             "total_internal_second_tva"                        =>    $devis->getTotalInternalSecondTVA(),
  681.             "total_public_second_tva"                        =>    $devis->getTotalPublicSecondTVA(),
  682.             "total_internal_second_tva_after_discount"        =>    $devis->getTotalInternalSecondTVAAfterDiscount(),
  683.             "total_public_second_tva_after_discount"        =>    $devis->getTotalPublicSecondTVAAfterDiscount(),
  684.             "total_internal_third_tva"                        =>    $devis->getTotalInternalThirdTVA(),
  685.             "total_public_third_tva"                        =>    $devis->getTotalPublicThirdTVA(),
  686.             "total_internal_third_tva_after_discount"        =>    $devis->getTotalInternalThirdTVAAfterDiscount(),
  687.             "total_public_third_tva_after_discount"            =>    $devis->getTotalPublicThirdTVAAfterDiscount(),
  688.             "solde_internal"                                =>    $devis->getSoldeInternal(),
  689.             "solde_public"                                    =>    $devis->getSoldePublic(),
  690.             "products"                                        =>    $products,
  691.         );
  692.     }
  693.     // Persist, No flush
  694.     public function convertSimulation($simulation)
  695.     {
  696.         // Plan.io Task #4328
  697.         $ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
  698.         $canApplyEcobonus = $ecobonusIsActive;
  699.         if ($simulation->hasEcoBonus())
  700.         {
  701.             $canApplyEcobonus = true;
  702.         }
  703.         $devis = new Devis();
  704.         $societyGroup = $simulation->getSocietyGroup();
  705.         if ($societyGroup === null)
  706.         {
  707.             // This should not happen
  708.             $this->logTools->errorlog('SocietyGroup is null for devis '.$simulation->displayForLog());
  709.             return null;
  710.         }
  711.         // Ref will be generated in the Controller
  712.         // Author will be set in the Controller
  713.         // Simulations shouldn't have invoices, installments, or be attached to tasks
  714.         $devis->setSociety($simulation->getSociety());
  715.         $devis->setEmitter($simulation->getEmitter());
  716.         // Plan.io Task #4571
  717.         // Receiver should be set before Mission when creating a new Devis
  718.         // in order to fetch the Zone correctly (at least for the time being)
  719.         $devis->setReceiver($simulation->getReceiver());
  720.         $devis->setMission($simulation->getMission());
  721.         $devis->setTitle($simulation->getTitle());
  722.         $devis->setInfo($simulation->getInfo());
  723.         $devis->setRefOrder($simulation->getRefOrder());
  724.         $devis->setMentionInfo($simulation->getMentionInfo());
  725.         $devis->setAnnulledInfo($simulation->getAnnulledInfo());
  726.         $devis->setPercentageInvoicing($simulation->getPercentageInvoicing());
  727.         $devis->setTemplate($simulation->getTemplate());
  728.         $devis->setManager($simulation->getManager());
  729.         $defaultStatus = $this->em->getRepository(DevisStatus::class)
  730.             ->findOneBy(array(
  731.                 'societyGroup'            =>    $societyGroup,
  732.                 'inProgress'            =>    1
  733.             ));
  734.         if ($defaultStatus !== null)
  735.         {
  736.             $devis->setStatus($defaultStatus);
  737.         }
  738.         else
  739.         {
  740.             // This should not happen
  741.             $this->logTools->errorlog('InProgress DevisStatus is null');
  742.             return null;
  743.         }
  744.         $devis->setOldStatus($simulation->getStatus());
  745.         $devis->setTva($simulation->getTva());
  746.         $devis->setSecondTva($simulation->getSecondTva());
  747.         $devis->setThirdTva($simulation->getThirdTva());
  748.         // Products
  749.         foreach ($simulation->getProducts() as $product)
  750.         {
  751.             $devisProduct = new DevisProduct();
  752.             $devisProduct = $this->copyDevisProduct($product, $devisProduct);
  753.             if ($canApplyEcobonus && $product->hasEcoBonus())
  754.             {
  755.                 $devisProduct->setEcobonus($product->getEcobonus());
  756.             }
  757.             $devis->addProduct($devisProduct);
  758.         }
  759.         // Plan.io Task #4318
  760.         if ($canApplyEcobonus)
  761.         {
  762.             $devis->computeEcobonus();
  763.         }
  764.         $devis->computeTotals();
  765.         // Addresses
  766.         $devis->setInterventionAddress($this->addressTools->duplicateAddress($simulation->getInterventionAddress()));
  767.         $devis->setBillingAddress($this->addressTools->duplicateAddress($simulation->getBillingAddress()));
  768.         // Attachments
  769.         foreach ($simulation->getAttachments() as $attachment)
  770.         {
  771.             // Move attachment
  772.             $attachment->setDirectUpload(1);
  773.             $attachment->setDevis($devis);
  774.         }
  775.         if ($devis->getTemplate() !== null)
  776.         {
  777.             // Plan.io Task #4593
  778.             // There can be maximum 2 associated IkeaTemplates
  779.             $ikeaTemplates = $this->em->getRepository(IkeaTemplateProduct::class)
  780.                 ->getIkeaTemplatesForPlatformTemplate($devis->getTemplate());
  781.             if (count($ikeaTemplates) > 0)
  782.             {
  783.                 $devis->setIkeaTemplate($ikeaTemplates[0]);
  784.                 if (count($ikeaTemplates) > 1)
  785.                 {
  786.                     $devis->setIkeaSecondTemplate($ikeaTemplates[1]);
  787.                 }
  788.             }
  789.         }
  790.         $devis->setOriginalSimulation($simulation);
  791.         $simulation->setResultingDevis($devis);
  792.         return $devis;
  793.     }
  794.     // Persist, No flush
  795.     public function convertUnfinishedSimulation($simulation, $mission, $author)
  796.     {
  797.         // Plan.io Task #4328
  798.         $ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
  799.         $canApplyEcobonus = $ecobonusIsActive;
  800.         if ($simulation->hasEcoBonus())
  801.         {
  802.             $canApplyEcobonus = true;
  803.         }
  804.         if ($simulation->getSocietyGroup() === null)
  805.         {
  806.             // This should not happen
  807.             $this->logTools->errorlog('SocietyGroup is null for Devis '.$simulation->displayForLog());
  808.             return null;
  809.         }
  810.         $devis = new Devis();
  811.         // We are creating a simulation, not an actual devis
  812.         $devis->setSimulation(1);
  813.         // Ref will be generated in the Controller
  814.         // Simulations shouldn't have invoices, installments, or be attached to tasks
  815.         $devis->setAuthor($author);
  816.         $devis->setSociety($mission->getSociety());
  817.         $devis->setEmitter($mission->getEmitter());
  818.         // Plan.io Task #4571
  819.         // Receiver should be set before Mission when creating a new Devis
  820.         // in order to fetch the Zone correctly (at least for the time being)
  821.         $devis->setReceiver($mission->getReceiver());
  822.         $devis->setMission($mission);
  823.         $devis->setTitle($simulation->getTitle());
  824.         $devis->setInfo($simulation->getInfo());
  825.         $devis->setRefOrder($simulation->getRefOrder());
  826.         $devis->setMentionInfo($simulation->getMentionInfo());
  827.         $devis->setAnnulledInfo($simulation->getAnnulledInfo());
  828.         $devis->setPercentageInvoicing($simulation->getPercentageInvoicing());
  829.         $devis->setTemplate($simulation->getTemplate());
  830.         if ($mission->getReceiver() !== null && $mission->getReceiver()->getIndividual() !== null)
  831.             $devis->setManager($mission->getReceiver()->getIndividual()->getManager());
  832.         if ($devis->getManager() === null)
  833.             $devis->setManager($author);
  834.         $defaultStatus = $this->em->getRepository(DevisStatus::class)
  835.             ->findOneBy(array(
  836.                 'simulation'            =>    1,
  837.                 'societyGroup'            =>    $simulation->getSocietyGroup(),
  838.             ));
  839.         if ($defaultStatus !== null)
  840.         {
  841.             $devis->setStatus($defaultStatus);
  842.         }
  843.         else
  844.         {
  845.             // This should not happen
  846.             $this->logTools->errorlog('InProgress DevisStatus is null');
  847.             return null;
  848.         }
  849.         $devis->setOldStatus($simulation->getStatus());
  850.         $devis->setTva($simulation->getTva());
  851.         // Products
  852.         foreach ($simulation->getProducts() as $product)
  853.         {
  854.             $devisProduct = new DevisProduct();
  855.             $devisProduct = $this->copyDevisProduct($product, $devisProduct);
  856.             // Plan.io Task #4318
  857.             if ($canApplyEcobonus && $product->hasEcoBonus())
  858.             {
  859.                 $devisProduct->setEcobonus($product->getEcobonus());
  860.             }
  861.             $devis->addProduct($devisProduct);
  862.         }
  863.         // Plan.io Task #4318
  864.         if ($canApplyEcobonus)
  865.         {
  866.             $devis->computeEcobonus();
  867.         }
  868.         $devis->computeTotals();
  869.         // Addresses
  870.         if ($mission->getReceiver() !== null)
  871.         {
  872.             $devis->setInterventionAddress($this->addressTools->duplicateAddress($mission->getReceiver()->getAddress()));
  873.             $devis->setBillingAddress($this->addressTools->duplicateAddress($mission->getReceiver()->getBillingAddress()));
  874.         }
  875.         // Attachments
  876.         foreach ($simulation->getAttachments() as $attachment)
  877.         {
  878.             // Move attachment
  879.             $attachment->setDirectUpload(1);
  880.             $attachment->setDevis($devis);
  881.         }
  882.         if ($devis->getTemplate() !== null)
  883.         {
  884.             // Plan.io Task #4593
  885.             // There can be maximum 2 associated IkeaTemplates
  886.             $ikeaTemplates = $this->em->getRepository(IkeaTemplateProduct::class)
  887.                 ->getIkeaTemplatesForPlatformTemplate($devis->getTemplate());
  888.             if (count($ikeaTemplates) > 0)
  889.             {
  890.                 $devis->setIkeaTemplate($ikeaTemplates[0]);
  891.                 if (count($ikeaTemplates) > 1)
  892.                 {
  893.                     $devis->setIkeaSecondTemplate($ikeaTemplates[1]);
  894.                 }
  895.             }
  896.         }
  897.         // $devis->setOriginalSimulation($simulation);
  898.         // $simulation->setResultingDevis($devis);
  899.         // Remove the old simulation
  900.         $this->em->remove($simulation);
  901.         return $devis;
  902.     }
  903.     public function copyDevisProduct($from, $to)
  904.     {
  905.         $to->setRef($from->getRef());
  906.         $to->setTitle($from->getTitle());
  907.         $to->setDescription($from->getDescription());
  908.         $to->setPublicPrice($from->getPublicPrice());
  909.         $to->setInternalPrice($from->getInternalPrice());
  910.         $to->setQuantity($from->getQuantity());
  911.         $to->setPercentage($from->getPercentage());
  912.         $to->setInvoiced($from->getInvoiced());
  913.         $to->setInvoicedPercentage($from->getInvoicedPercentage());
  914.         $to->setTotalPublicHT($from->getTotalPublicHT());
  915.         $to->setTotalInternalHT($from->getTotalInternalHT());
  916.         $to->setTotalPublicTTC($from->getTotalPublicTTC());
  917.         $to->setTotalInternalTTC($from->getTotalInternalTTC());
  918.         $to->setDiscount($from->getDiscount());
  919.         $to->setReadonly($from->getReadonly());
  920.         $to->setSamePrice($from->getSamePrice());
  921.         $to->setOptionNoPrice($from->getOptionNoPrice());
  922.         $to->setUnit($from->getUnit());
  923.         $to->setProduct($from->getProduct());
  924.         $to->setQuestion($from->getQuestion());
  925.         $to->setOriginalProduct($from->getOriginalProduct());
  926.         $to->setPosition($from->getPosition());
  927.         $to->setTva($from->getTva());
  928.         $to->setOptionNoPrice($from->getOptionNoPrice());
  929.         // Plan.io Task #4561
  930.         $to->setLinearMetersLower($from->getLinearMetersLower(true));
  931.         $to->setLinearMetersUpper($from->getLinearMetersUpper(true));
  932.         $to->setLinearMetersColumn($from->getLinearMetersColumn(true));
  933.         $to->setLinearMetersIsland($from->getLinearMetersIsland(true));
  934.         $to->setLinearMetersIntegratedAppliance($from->getLinearMetersIntegratedAppliance(true));
  935.         return $to;
  936.     }
  937.     // Plan.io Task #4624 : This is also called from NotebookTools :: updateNotebook_additionalWork
  938.     public function handleProductsFromForm($devis, $formProducts, $editMode = false)
  939.     {
  940.         // Plan.io Task #4328
  941.         $ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
  942.         $canApplyEcobonus = $ecobonusIsActive;
  943.         // Plan.io Task #3800
  944.         $titlePositions = array();
  945.         $resultProducts = array();
  946.         if ($editMode)
  947.         {
  948.             // Plan.io Task #4328
  949.             if ($devis->hasEcoBonus())
  950.             {
  951.                 $canApplyEcobonus = true;
  952.             }
  953.             foreach ($devis->getProducts() as $product)
  954.             {
  955.                 // Plan.io Task #3626
  956.                 // We need to remove the ref of this product from all InvoiceProducts it may reference
  957.                 // This should not pose a problem since
  958.                 // - if the invoice is stil valid, then we cannot edit corresponding devis, thus we should not be here
  959.                 // - if the invoice has been annulled by a credit invoice, then the invoice is no longer editable
  960.                 $invoiceProducts = $this->em->getRepository(InvoiceProduct::class)->findByDevisProduct($product);
  961.                 foreach ($invoiceProducts as $iProduct)
  962.                 {
  963.                     $iProduct->setDevisProduct(null);
  964.                 }
  965.                 $devis->removeProduct($product);
  966.                 $this->em->remove($product);
  967.             }
  968.             // Plan.io Task #4328
  969.             // Need to reset ecobonus (Don't check if module is active here)
  970.             $devis->setEcoBonus(null);
  971.             // Plan.io Task #3800
  972.             // Get the original positions of the titles before updating the Devis
  973.             // Set them as keys of the array
  974.             // so we can insert the products on the missing array keys in order of arrival
  975.             foreach ($devis->getTitles() as $key => $title)
  976.             {
  977.                 $titlePositions[$title->getPosition()] = $title->getPosition();
  978.             }
  979.         }
  980.         if (empty($formProducts))
  981.             return null;
  982.         $formProducts = json_decode($formProducts, true);
  983.         if (empty($formProducts))
  984.             return null;
  985.         $productsRepository = $this->em->getRepository(Product::class);
  986.         $unitRepository = $this->em->getRepository(Unit::class);
  987.         // The discount, if any, should be last
  988.         $discount = null;
  989.         $ecoBonusProduct = null;
  990.         // Take the position into account
  991.         foreach($formProducts as $product)
  992.         {
  993.             if (array_key_exists('id', $product) &&
  994.                 array_key_exists('ref', $product) &&
  995.                 array_key_exists('quantity', $product) &&
  996.                 array_key_exists('internal_price', $product) &&
  997.                 array_key_exists('public_price', $product) &&
  998.                 array_key_exists('title', $product) &&
  999.                 array_key_exists('info', $product) &&
  1000.                 array_key_exists('unit', $product))
  1001.             {
  1002.                 $product_id = $product['id'];
  1003.                 $product_ref = $product['ref'];
  1004.                 $product_qt = $product['quantity'];
  1005.                 $product_internal_price = $product['internal_price'];
  1006.                 $product_public_price = $product['public_price'];
  1007.                 $product_title = $product['title'];
  1008.                 $product_info = $product['info'];
  1009.                 $product_unit = $product['unit'];
  1010.                 $product_ecobonus = 0;
  1011.                 if (array_key_exists('ecobonus', $product))
  1012.                 {
  1013.                     $product_ecobonus = floatval($product['ecobonus']);
  1014.                 }
  1015.                 // Handle free product
  1016.                 if (intval($product_id) == -1)
  1017.                 {
  1018.                     $product = $productsRepository->findOneBy(array(
  1019.                         'societyGroup'        =>    $devis->getSocietyGroup(),
  1020.                         'free'                =>    1,
  1021.                     ));
  1022.                 }
  1023.                 else
  1024.                 {
  1025.                     $product = $productsRepository->findOneById($product_id);
  1026.                 }
  1027.                 $product_unit = $unitRepository->findOneById($product_unit);
  1028.                 if ($product === null)
  1029.                     continue;
  1030.                 if ($product_qt <= 0)
  1031.                     continue;
  1032.                 if ($product_internal_price === null)
  1033.                     $product_internal_price = 0;
  1034.                 if ($product_public_price === null)
  1035.                     $product_public_price = 0;
  1036.                 // Discount ?
  1037.                 if ($product->isDiscount() && $discount !== null)
  1038.                 {
  1039.                     // This should not happen
  1040.                     // Only one discount is allowed for a devis
  1041.                     continue;
  1042.                 }
  1043.                 // Plan.io Task #4328
  1044.                 if ($canApplyEcobonus && $ecoBonusProduct !== null && !empty($product_ecobonus))
  1045.                 {
  1046.                     // This should not happen
  1047.                     // Only one EcoProduct is allowed for a devis
  1048.                     continue;
  1049.                 }
  1050.                 // Product
  1051.                 $dp = new DevisProduct();
  1052.                 // Plan.io Task #3800
  1053.                 // Save for position
  1054.                 $resultProducts[] = $dp;
  1055.                 $dp->setOriginalProduct($product);
  1056.                 if ($editMode)
  1057.                     $dp->setRef($product_ref);
  1058.                 else
  1059.                     $dp->setRef($product->getRef());
  1060.                 $dp->setTitle($product->getTitle());
  1061.                 $dp->setDescription($product->getDescription());
  1062.                 $dp->setOptionNoPrice($product->getOptionNoPrice());
  1063.                 $dp->setTva($product->getTva());
  1064.                 $dp->setRefCost($product->getRefCost());
  1065.                 // Check if the product can be edited, if not use product value
  1066.                 if ($product->getReadonly())
  1067.                 {
  1068.                     // Not editable
  1069.                     // $dp->setPublicPrice($product->getPublicPrice());
  1070.                     // $dp->setInternalPrice($product->getInternalPrice());
  1071.                     // Plan.io Task #3345
  1072.                     $dp->setPublicPrice($product_public_price);
  1073.                     $dp->setInternalPrice($product_internal_price);
  1074.                     $dp->setTitle($product->getTitle());
  1075.                     $dp->setDescription($product->getDescription());
  1076.                 }
  1077.                 else
  1078.                 {
  1079.                     // Editable
  1080.                     // Set title
  1081.                     $dp->setTitle($product_title);
  1082.                     $dp->setDescription($product_info);
  1083.                     // Set price, taking into account 'samePrice' constraint
  1084.                     if ($product->getSamePrice())
  1085.                     {
  1086.                         // Both prices must be the same,
  1087.                         // Get the value given by the user
  1088.                         $dp->setPublicPrice($product_internal_price);
  1089.                         $dp->setInternalPrice($product_internal_price);
  1090.                     }
  1091.                     else
  1092.                     {
  1093.                         // Prices can be different
  1094.                         $dp->setPublicPrice($product_public_price);
  1095.                         $dp->setInternalPrice($product_internal_price);
  1096.                     }
  1097.                 }
  1098.                 if ($product_unit === null)
  1099.                 {
  1100.                     $product_unit = $product->getUnit();
  1101.                 }
  1102.                 $dp->setUnit($product_unit);
  1103.                 $dp->setQuantity($product_qt);
  1104.                 $dp->setQuestion($product->getQuestion());
  1105.                 $dp->setReadonly($product->getReadonly());
  1106.                 $dp->setSamePrice($product->getSamePrice());
  1107.                 // Plan.io Task #4561
  1108.                 $dp->setLinearMetersLower($product->getLinearMetersLower(true));
  1109.                 $dp->setLinearMetersUpper($product->getLinearMetersUpper(true));
  1110.                 // Plan.io Task #4592
  1111.                 $dp->setLinearMetersColumn($product->getLinearMetersColumn(true));
  1112.                 $dp->setLinearMetersIsland($product->getLinearMetersIsland(true));
  1113.                 $dp->setLinearMetersIntegratedAppliance($product->getLinearMetersIntegratedAppliance(true));
  1114.                 // Plan.io Task #4328
  1115.                 if ($canApplyEcobonus && !empty($product_ecobonus))
  1116.                 {
  1117.                     $dp->setEcoBonus($product_ecobonus);
  1118.                     $ecoBonusProduct = $dp;
  1119.                 }
  1120.                 // Plan.io Task #3343
  1121.                 // Only consider internal prices if
  1122.                 //         - devis/mission is JCAF
  1123.                 //        - devis.emitter = devis.receiver
  1124.                 if ($devis->mustHaveEqualPrices())
  1125.                 {
  1126.                     // Do some logging
  1127.                     if ($dp->getPublicPrice() != $dp->getInternalPrice())
  1128.                     {
  1129.                         $this->logTools->errorlog("PublicPrice # InternalPrice where it should not be for devis ".$devis->displayForLog());
  1130.                     }
  1131.                     // Only keep the InternalPrice
  1132.                     $dp->setPublicPrice($dp->getInternalPrice());
  1133.                     $dp->setSamePrice(1);
  1134.                 }
  1135.                 if ($product->isDiscount())
  1136.                 {
  1137.                     // The discount, if any, should be last
  1138.                     $dp->setDiscount(1);
  1139.                     $discount = $dp;
  1140.                 }
  1141.                 else
  1142.                 {
  1143.                     $devis->addProduct($dp);
  1144.                 }
  1145.             }
  1146.             // Now add the discount, if any
  1147.             if ($discount !== null)
  1148.             {
  1149.                 $devis->addProduct($discount);
  1150.             }
  1151.         }
  1152.         // Plan.io Task #4328
  1153.         if ($canApplyEcobonus)
  1154.         {
  1155.             $devis->computeEcobonus();
  1156.         }
  1157.         // Plan.io Task #3800
  1158.         // Set positions
  1159.         // Insert the products on the missing array keys in order of arrival
  1160.         // The existing keys represent the existing titles
  1161.         $nb = count($titlePositions) + count($resultProducts);
  1162.         $key = 0;
  1163.         for ($pos = 1 ; $pos <= $nb ; $pos++)
  1164.         {
  1165.             if (array_key_exists($pos, $titlePositions))
  1166.             {
  1167.             }
  1168.             else
  1169.             {
  1170.                 $titlePositions[$pos] = $resultProducts[$key];
  1171.                 $key ++;
  1172.             }
  1173.         }
  1174.         foreach ($titlePositions as $pos => $item)
  1175.         {
  1176.             if ($item instanceof DevisProduct)
  1177.             {
  1178.                 $item->setPosition($pos);
  1179.             }
  1180.         }
  1181.         return true;
  1182.     }
  1183.     // No Flush, Persist only
  1184.     public function updateRefOrders($data)
  1185.     {
  1186.         if (empty($data))
  1187.             return false;
  1188.         $devisRep = $this->em->getRepository(Devis::class);
  1189.         // Handle data
  1190.         foreach ($data as $devisItem)
  1191.         {
  1192.             $id = $devisItem[0];
  1193.             $value = $devisItem[1];
  1194.             // Get the devis
  1195.             $devis = $devisRep->findOneById($id);
  1196.             if ($devis === null)
  1197.                 continue;
  1198.             $oldValue = $devis->getRefOrder();
  1199.             if ($oldValue != $value)
  1200.             {
  1201.                 // Update refOrder
  1202.                 $devis->setRefOrder($value);
  1203.                 $this->em->persist($devis);
  1204.                 $devis->setLoggingData(["info" => $this->translator->trans('updated_via_task')]);
  1205.             }
  1206.         }
  1207.         return true;
  1208.     }
  1209.     // No Flush, Persist only
  1210.     public function updateRefOrder($devis, $refOrder)
  1211.     {
  1212.         if ($devis === null)
  1213.             return false;
  1214.         $backTrace = debug_backtrace()[0];
  1215.         $method = $backTrace['function'];
  1216.         $class = $backTrace['class'];
  1217.         $trace = $class . "::" . $method;
  1218.         $trace = str_replace("\\", "::", $trace);
  1219.         $oldValue = $devis->getRefOrder();
  1220.         if ($oldValue != $refOrder)
  1221.         {
  1222.             // Update refOrder
  1223.             $devis->setRefOrder($refOrder);
  1224.             $this->em->persist($devis);
  1225.             // Plan.io Task #3922
  1226.             $devis->setLoggingData(["info" => $this->translator->trans('devis_edit_ref_order_via_invoice')]);
  1227.         }
  1228.         return true;
  1229.     }
  1230.     // Plan.io Task #3621
  1231.     // No Flush, Persist only
  1232.     public function updateIkeaOrderNumber($devis, $refOrder)
  1233.     {
  1234.         if ($devis === null)
  1235.             return false;
  1236.         $oldValue = $devis->getIkeaOrderNumber();
  1237.         if ($oldValue != $refOrder)
  1238.         {
  1239.             // Update refOrder
  1240.             $devis->setIkeaOrderNumber($refOrder);
  1241.             $this->em->persist($devis);
  1242.             // Plan.io Task #3922
  1243.             $devis->setLoggingData(["info" => $this->translator->trans('devis_edit_ref_order_via_invoice')]);
  1244.         }
  1245.         return true;
  1246.     }
  1247.     // No Flush
  1248.     public function updateStatusConsideringInvoicing($args)
  1249.     {
  1250.         // Get all the arguments
  1251.         // Devis is mandatory
  1252.         if (array_key_exists("devis", $args))                $devis = $args["devis"];
  1253.         else                                                return null;
  1254.         if (array_key_exists("info", $args))                $info = $args["info"];
  1255.         else                                                  $info = null;
  1256.         if ($devis->isAnnulled())
  1257.             return false;
  1258.         // Plan.io Task #4326
  1259.         // Just in case
  1260.         if (array_key_exists("invoice", $args))
  1261.         {
  1262.             $invoice = $args["invoice"];
  1263.             if ($invoice->isDraft())
  1264.             {
  1265.                 // Nothing to do here => Bye bye
  1266.                 return false;
  1267.             }
  1268.         }
  1269.         // $this->logTools->ploopLog("[#4326][".$devis->getId()."][".$devis->getRef()."] DevisTools :: updateStatusConsideringInvoicing");
  1270.         $invoicing = $this->checkInvoicing($devis);
  1271.         // If Empty or Not Invoiced, do nothing
  1272.         if ($invoicing["empty"] || $invoicing["not_invoiced"])
  1273.             return false;
  1274.         $status = $invoicing["status"];
  1275.         if ($status !== null)
  1276.         {
  1277.             // $oldStatus = $devis->getStatus()->getValue();
  1278.             // $newStatus = $status->getValue();
  1279.             $devis->setStatus($status);
  1280.             return true;
  1281.         }
  1282.         return false;
  1283.     }
  1284.     // Plan.io Task #3898.Zones.ZoneProducts.iTasks
  1285.     // Rekto : InvoiceTools :: updateDevisTaskStatus
  1286.     // Plan.io Task #4326 : Added $invoice param
  1287.     public function updateDevisTaskStatus(Devis $devis, Invoice $invoice)
  1288.     {
  1289.         // $this->logTools->ploopLog("[#4326][".$devis->getId()."][".$devis->getRef()."] DevisTools :: updateDevisTaskStatus");
  1290.         // Code should be applied only for Rekto Group
  1291.         if ($devis->getSocietyGroup() === null || $devis->getSocietyGroup()->isRekto() === false)
  1292.         {
  1293.             return false;
  1294.         }
  1295.         if ($devis->isAnnulled())
  1296.             return false;
  1297.         // Only change task status for devis which are totally invoiced
  1298.         if (!$devis->isInvoiced())
  1299.             return false;
  1300.         // Plan.io Task #4326
  1301.         // Just in case
  1302.         if ($invoice->isDraft())
  1303.         {
  1304.             // Nothing to do here => Bye bye
  1305.             return false;
  1306.         }
  1307.         $taskStatusRepository = $this->em->getRepository(TaskStatus::class);
  1308.         $invoicedFinished = $taskStatusRepository->findOneBy(array(
  1309.             'societyGroup'        =>    $devis->getSocietyGroup(),
  1310.             'iTaskAuto'            =>    1,
  1311.             'code'                =>    TaskStatus::TASK_STATUS_INVOICED_FINISHED,
  1312.         ));
  1313.         if ($invoicedFinished === null)
  1314.         {
  1315.             return false;
  1316.         }
  1317.         $newStatus = $invoicedFinished->getValue();
  1318.         foreach ($devis->getTasks() as $task)
  1319.         {
  1320.             $tochange = true;
  1321.             // check if task has another not invoiced devis
  1322.             foreach($task->getDevisGroup() as $taskDevis)
  1323.             {
  1324.                 if (!$devis->equals($taskDevis) && !$taskDevis->getStatus()->isInvoiced())
  1325.                 {
  1326.                     $tochange = false;
  1327.                 }
  1328.             }
  1329.             if ($tochange === false)
  1330.             {
  1331.                 continue;
  1332.             }
  1333.             $oldStatus = $task->getStatus()->getValue();
  1334.             $task->setStatus($invoicedFinished);
  1335.             // Plan.io Task #3865
  1336.             // This one does not flush
  1337.             $this->advancedNotificationTools->updateAdvancedNotificationForTask($task);
  1338.         }
  1339.         return true;
  1340.     }
  1341.     // Plan.io Task #3898.Zones.ZoneProducts.iTasks
  1342.     // Rekto : DevisTools :: handleStatusChangeForTask
  1343.     public function handleDevisStatusChangeForTask(Task $task)
  1344.     {
  1345.         $output = [
  1346.             'status'         => false,
  1347.             'devis_updated'    => [],
  1348.         ];
  1349.         $devisUpdated = [];
  1350.         if (!$this->security->isGranted('devis_is_active'))
  1351.         {
  1352.             // Module not active, exit
  1353.             return $output;
  1354.         }
  1355.         $societyGroup = $task->getSocietyGroup();
  1356.         if ($societyGroup === null)
  1357.         {
  1358.             // Should never happen
  1359.             $this->logTools->errorLog('SocietyGroup is null for task : ' . $task->displayForLog());
  1360.             return $output;
  1361.         }
  1362.         $devisStatusRepository = $this->em->getRepository(DevisStatus::class);
  1363.         $inProgressStatus = $devisStatusRepository->findOneBy(array(
  1364.             'societyGroup'    =>    $societyGroup,
  1365.             'inProgress'    =>    1,
  1366.         ));
  1367.         $plannedStatus = $devisStatusRepository->findOneBy(array(
  1368.             'societyGroup'    =>    $societyGroup,
  1369.             'planned'        =>    1,
  1370.         ));
  1371.         if ($inProgressStatus === null || $plannedStatus === null)
  1372.         {
  1373.             // Should never happen
  1374.             $this->logTools->errorLog('Status planned or inprogress not found in societyGroup : ' . $societyGroup->displayForLog());
  1375.             return $output;
  1376.         }
  1377.         $devisGroup = $task->getDevisGroup();
  1378.         if ($devisGroup->isDirty() == false)
  1379.         {
  1380.             // $this->logTools->ploopLog('Is not dirty');
  1381.             $output['status'] = true;
  1382.             return $output;
  1383.         }
  1384.         // Get trace for log
  1385.         $backTrace = debug_backtrace()[0];
  1386.         $method = $backTrace['function'];
  1387.         $class = $backTrace['class'];
  1388.         $trace = $class . "::" . $method;
  1389.         $trace = str_replace("\\", "::", $trace);
  1390.         // Handle add case : DevisStatus :: planned
  1391.         foreach ($devisGroup as $key => $devis)
  1392.         {
  1393.             $isOldDevis = false;
  1394.             foreach ($devisGroup->getSnapshot() as $snapshotDevis)
  1395.             {
  1396.                 if ($devis->equals($snapshotDevis))
  1397.                 {
  1398.                     $isOldDevis = true;
  1399.                     break;
  1400.                 }
  1401.             }
  1402.             if ($isOldDevis)
  1403.             {
  1404.                 // Nothing to do here
  1405.                 continue;
  1406.             }
  1407.             if ($devis->isInProgress() === false)
  1408.             {
  1409.                 // Nothing to do here
  1410.                 continue;
  1411.             }
  1412.             // Plan.io Task #3922
  1413.             $devis->setLoggingData(["info" => "devis_auto_edit_status_added_to_task"]);
  1414.             $devis->setStatus($plannedStatus);
  1415.             $devisUpdated[] = $devis;
  1416.         }
  1417.         // Handle devis removed
  1418.         foreach ($task->getDevisGroup()->getSnapshot() as $snapshotDevis)
  1419.         {
  1420.             $isOldDevis = false;
  1421.             foreach ($devisGroup as $devis)
  1422.             {
  1423.                 if ($snapshotDevis->equals($devis))
  1424.                 {
  1425.                     $isOldDevis = true;
  1426.                     break;
  1427.                 }
  1428.             }
  1429.             if ($isOldDevis)
  1430.             {
  1431.                 // Nothing to do here
  1432.                 continue;
  1433.             }
  1434.             $devis = $snapshotDevis;
  1435.             // Only do this if devis is 'planned' and not in another task (#2639)
  1436.             // Here getTasks should be 0 if devis had a single task
  1437.             if ($devis->isPlanned() === false || sizeof($devis->getTasks()) > 0)
  1438.             {
  1439.                 // Nothing to do here
  1440.                 continue;
  1441.             }
  1442.             // Devis has been removed,
  1443.             // Revert it to old status, if available
  1444.             // Revert to default status if not
  1445.             $devisStatus = $inProgressStatus;
  1446.             if ($devis->getOldStatus() !== null && $devis->getOldStatus()->isPlanned() === false)
  1447.             {
  1448.                 $devisStatus = $devis->getOldStatus();
  1449.             }
  1450.             // Plan.io Task #3922
  1451.             $devis->setLoggingData(["info" => "devis_auto_edit_status_removed_from_task"]);
  1452.             $devis->setStatus($devisStatus);
  1453.             $devisUpdated[] = $devis;
  1454.         }
  1455.         $output['status'] = true;
  1456.         $output['devis_updated'] = $devisUpdated;
  1457.         return $output;
  1458.     }
  1459.     // This checks to see if the devis
  1460.     // is not invoiced, partially invoiced or totally invoiced
  1461.     // based on the state of its products
  1462.     public function checkInvoicing($devis)
  1463.     {
  1464.         if ($devis->getProducts()->count() < 1)
  1465.             return array(
  1466.                 "empty"                    =>    true,
  1467.                 "not_invoiced"            =>    false,
  1468.                 "partially_invoiced"    =>    false,
  1469.                 "invoiced"                =>    false,
  1470.                 "status"                =>    null,
  1471.             );
  1472.         $statusRepository = $this->em->getRepository(DevisStatus::class);
  1473.         $statusPartiallyInvoiced = $statusRepository
  1474.             ->findOneBy(array(
  1475.                 'partiallyInvoiced'            =>    1,
  1476.                 'societyGroup'                =>    $devis->getSocietyGroup(),
  1477.             ));
  1478.         $statusInvoiced = $statusRepository
  1479.             ->findOneBy(array(
  1480.                 'invoiced'                    =>    1,
  1481.                 'societyGroup'                =>    $devis->getSocietyGroup(),
  1482.             ));
  1483.         $totalQuantity = 0;
  1484.         $totalInvoiced = 0;
  1485.         foreach($devis->getProducts() as $product)
  1486.         {
  1487.             if ($product->isNotDiscount())
  1488.             {
  1489.                 $totalQuantity += $product->getQuantity();
  1490.                 $totalInvoiced += $product->getInvoiced();
  1491.             }
  1492.         }
  1493.         // Not invoiced
  1494.         if ($totalInvoiced == 0)
  1495.         {
  1496.             return array(
  1497.                 "empty"                    =>    false,
  1498.                 "not_invoiced"            =>    true,
  1499.                 "partially_invoiced"    =>    false,
  1500.                 "invoiced"                =>    false,
  1501.                 "status"                =>    null,
  1502.             );
  1503.         }
  1504.         // Partially invoiced
  1505.         if ($totalInvoiced < $totalQuantity)
  1506.         {
  1507.             return array(
  1508.                 "empty"                    =>    false,
  1509.                 "not_invoiced"            =>    false,
  1510.                 "partially_invoiced"    =>    true,
  1511.                 "invoiced"                =>    false,
  1512.                 "invoiced"                =>    false,
  1513.                 "status"                =>    $statusPartiallyInvoiced,
  1514.             );
  1515.         }
  1516.         // If we are here it means that the devis has at least one product
  1517.         // and the total Invoiced is equal to the total Quantity
  1518.         // Plan.io Task #4326 : Or higher :)
  1519.         return array(
  1520.             "empty"                    =>    false,
  1521.             "not_invoiced"            =>    false,
  1522.             "partially_invoiced"    =>    false,
  1523.             "invoiced"                =>    true,
  1524.             "status"                =>    $statusInvoiced,
  1525.         );
  1526.     }
  1527.     // Plan.io Task #4353
  1528.     // Lists invoices considered annulled (excluding itself) linked to the same devis in the invoice view.
  1529.     public function getListingInvoiceWithCredit($devis, $invoice)
  1530.     {
  1531.         $invoicesAnnulled = [];
  1532.         $devisInvoices = $devis->getInvoicesOnly();
  1533.         foreach ($devisInvoices as $devisInvoice)
  1534.         {
  1535.             if ($invoice === $devisInvoice)
  1536.             {
  1537.                 break;
  1538.             }
  1539.             // We are looking to see if a credit note is linked to this invoice
  1540.             $devisCredit = $this->em->getRepository(Invoice::class)->findOneBy(array(
  1541.                 'creditInvoice'        =>    $devisInvoice,
  1542.                 'draft'                =>    0,
  1543.             ));
  1544.             // If a credit note is found, the invoice is considered cancelled
  1545.             if ($devisCredit !== null)
  1546.             {
  1547.                 $invoicesAnnulled[] = $devisInvoice;
  1548.             }
  1549.         }
  1550.         return $invoicesAnnulled;
  1551.     }
  1552.     public function handlePositionForTitleCreation($title, $elements)
  1553.     {
  1554.         foreach($elements as $el)
  1555.         {
  1556.             if($el->getPosition() !== null)
  1557.             {
  1558.                 $el->setPosition($el->getPosition() + 1);
  1559.             }
  1560.         }
  1561.         $title->setPosition(1);
  1562.     }
  1563.     public function updatePositionTitleDelete($oldPos, $products, $titles)
  1564.     {
  1565.         // Sort by Position
  1566.         $mergedProducts = array_merge($products->toArray(), $titles->toArray());
  1567.         if (count($mergedProducts))
  1568.         {
  1569.             $iterator = $mergedProducts;
  1570.             uasort($mergedProducts,function ($first, $second)
  1571.             {
  1572.                 return (int) $first->getPosition() > (int) $second->getPosition() ? 1 : -1;
  1573.             });
  1574.         }
  1575.         // Apply -1 when old position is inferior at current position
  1576.         foreach ($mergedProducts as $mp)
  1577.         {
  1578.             if($oldPos < $mp->getPosition())
  1579.             {
  1580.                 $newPosition = $mp->getPosition() - 1;
  1581.                 $mp->setPosition($newPosition);
  1582.                 $this->em->persist($mp);
  1583.             }
  1584.         }
  1585.     }
  1586. }