src/Services/Platform/DevisTools.php line 59

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