<?php
//----------------------------------------------------------------------
// src/Services/Platform/DevisTools.php
//----------------------------------------------------------------------
namespace App\Services\Platform;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Services\Common\TextTools;
use App\Services\Location\AddressTools;
use App\Services\LogTools;
use App\Services\Product\ProductTools;
use App\Entity\Access;
use App\Entity\Common\Attachment;
use App\Entity\Ikea\Product as IkeaProduct;
use App\Entity\Ikea\TemplateProduct as IkeaTemplateProduct;
use App\Entity\Ikea\Template as IkeaTemplate;
use App\Entity\Ikea\TemplateData;
use App\DTO\Ikea\ViewMethod2026Data;
use App\Entity\Planning\Task;
use App\Entity\Platform\Devis\Devis;
use App\Entity\Platform\Devis\DevisProduct;
use App\Entity\Platform\Devis\DevisStatus;
use App\Entity\Platform\Invoice\Invoice;
use App\Entity\Platform\Invoice\InvoiceProduct;
use App\Entity\Planning\TaskStatus;
use App\Entity\Platform\Society;
use App\Entity\Product\Config as ProductConfig;
use App\Entity\Product\Product;
use App\Entity\Product\Unit;
use App\Entity\ProjectManager\ProjectNotebook;
use App\Services\Ding\AdvancedNotificationTools;
use App\Services\Product\ProductConfigTools;
class DevisTools
{
public function __construct(
ManagerRegistry $doctrine, TranslatorInterface $translator,
TextTools $textTools, AddressTools $addressTools,
ProductTools $productTools, Security $security, LogTools $logTools, AdvancedNotificationTools $advancedNotificationTools,
ProductConfigTools $productConfigTools
)
{
$this->em = $doctrine->getManager();
$this->translator = $translator;
$this->logTools = $logTools;
$this->textTools = $textTools;
$this->addressTools = $addressTools;
$this->productTools = $productTools;
$this->advancedNotificationTools = $advancedNotificationTools;
$this->productConfigTools = $productConfigTools;
$this->security = $security;
}
// Plan.io Task #4624
public function belongsToProjectManager(Devis $devis)
{
if ($devis->getProjectNotebook() !== null)
{
return true;
}
$notebook = $this->em->getRepository(ProjectNotebook::class)
->findOneByPreparatoryWorkDevis($devis);
if ($notebook !== null)
{
return true;
}
$notebook = $this->em->getRepository(ProjectNotebook::class)
->findOneByFinishingWorkDevis($devis);
if ($notebook !== null)
{
return true;
}
// ALl hope is lost
return false;
}
// Plan.io Task #4593
public function getLinearMeterData(Devis $devis)
{
$templateData = $devis->getIkeaTemplateData();
if ($templateData === null)
{
return null;
}
$methodVersion = intval($devis->getMethodVersion());
// Decide if 2025 or 2026
if ($methodVersion == 2026)
{
return $templateData->getViewMethod2026Dto();
}
if ($methodVersion == 2025)
{
return $templateData->getViewMethod2025Dto();
}
return null;
}
// Plan.io Task #4383
// We want a list of IkeaServiceOrders which meet all the following criteria
// - ServiceOrder is attached to Devis->Mission
// - ServiceOrder->orderNumber = Devis->ikeaOrderNumber
// - ServiceOrder->isAfterSalesService
// (ie. ServiceOrder->serviceCode in ServiceOrder::SAV_SERVICE_CODES)
public function getIkeaSavServiceOrders(Devis $devis)
{
if (!$this->devisIsIkeaSAV($devis))
{
// Not a Ikea SAV Devis => Die.Hard
return new ArrayCollection();
}
// At this point we know that
// - the Devis has a non empty ikeaOrderNumber
// - the Devis has a not null mission
// - the Mission has at least one Ikea ServiceOrder with a SAV ServiceCode
$devisIkeaOrderNumber = $devis->getIkeaOrderNumber();
$mission = $devis->getMission();
$ikeaServiceOrders = new ArrayCollection();
foreach ($mission->getIkeaServiceOrders() as $so)
{
if ($devis->getIkeaOrderNumber() != $so->getOrderNumber())
{
// Not the same Order Number => Skip
continue;
}
if (!$so->isAfterSalesService())
{
// Not a "SAV" Service Code => Skip
continue;
}
// All looks good
if ($ikeaServiceOrders->contains($so))
{
// Already added => Skip
continue;
}
$ikeaServiceOrders[] = $so;
}
return $ikeaServiceOrders;
}
// Plan.io Task #4383
// "Ikea SAV" Devis = a "timming" Devis linked to an "SAV" Ikea ServiceOrder
// Check Devis not annulled | refused
public function devisIsIkeaSAV(Devis $devis)
{
if ($devis->isAnnulled() || $devis->isRefused())
{
return false;
}
if (empty($devis->getIkeaOrderNumber()))
{
// Not a Ikea Devis => Die.Hard
return false;
}
if (!$devis->hasHourProduct())
{
// Not a "timming" Devis => Die.Hard
return false;
}
$mission = $devis->getMission();
if ($mission === null)
{
// This should not happen
return false;
}
if (empty($mission->getIkeaServiceOrders()))
{
// Not a Ikea Mission => Die.Hard
// However since the Devis has an ikeaOrderNumber this should not happen
return false;
}
foreach ($mission->getIkeaServiceOrders() as $ikeaServiceOrder)
{
if ($devis->getIkeaOrderNumber() != $ikeaServiceOrder->getOrderNumber())
{
// Not the same Order Number => Skip
continue;
}
if (!$ikeaServiceOrder->isAfterSalesService())
{
// Not a "SAV" Service Code => Skip
continue;
}
// If we are here it means we have
// a "timming" Devis linked to an "SAV" Ikea ServiceOrder
// => All looks good
return true;
}
// All hope is lost
return false;
}
// Plan.io Task #3793
public function sign(Devis $devis, Access $coAuthor)
{
$societyGroup = $devis->getSocietyGroup();
if ($societyGroup === null)
{
// This should not happen
$this->logTools->errorlog("SocietyGroup is null for Devis ".$devis->displayForLog());
return false;
}
if ($devis->hasSignature() === false)
{
// This should not happen
return false;
}
// Update Signature date and co-author
$devis->setSignatureDate(new \DateTime());
$devis->setSignatureCoAuthor($coAuthor);
// Change status to accepted
$acceptedStatus = $this->em->getRepository(DevisStatus::class)
->findOneBy(array(
'societyGroup' => $societyGroup,
'accepted' => 1,
));
if ($acceptedStatus === null)
{
// This should not happen
$this->logTools->errorlog("Accepted DevisStatus is null for societyGroup ".$societyGroup->displayForLog());
return false;
}
$devis->setStatus($acceptedStatus);
// All looks good
return true;
}
// Plan.io Task #2849
public function detectReInvoicingProblem($devis)
{
// The problem only arises when trying to re-invoice a devis with installments
if ($devis->getInstallments()->count() < 1)
{
return null;
}
// No invoice, no problem
// if ($devis->getInvoices()->count() < 1)
// Plan.io Task #4326 : Only consider actual invoices, and exclude drafts
if ($devis->getInvoicesOnly()->count() < 1)
{
return null;
}
// If we are here it means that the devis has installments and invoices
// Just check to see if any of them match
foreach ($devis->getInstallments() as $installment)
{
if ($installment->getLinkInvoice() !== null)
{
$invoice = $installment->getLinkInvoice()->getInvoice();
if ($invoice !== null)
{
// This installment is linked to an invoice
// Does the invoice have a credit invoice ?
// Plan.io Task #4326 : Do not take drafts into account
$credit = $this->em->getRepository(Invoice::class)->findOneBy(array(
'creditInvoice' => $invoice,
'draft' => 0,
));
if ($credit !== null)
{
return array(
'devis' => $devis,
'invoice' => $invoice,
'credit' => $credit,
);
}
}
}
}
return null;
}
/**
* Plan.io Task #3867 - Retrieves and calculates profitability data
*
* @param Devis $devis
* @return array {
* profitability: int,
* profitabilityPercent: int,
* totalCostProducts: int,
* interventionCost: int,
* totalCost: int,
* }
*/
public function getDataTiming(Devis $devis)
{
$totalCostProducts = 0;
foreach ($devis->getProducts() as $product)
{
$totalCostProducts += $product->getRefCost() * $product->getQuantity();
}
$interventionCost = 0;
$society = $devis->getSociety();
if ($society !== null)
{
// Switch for % or fixe value
if ($society->getInterventionCostType() == Society::INT_COST_TYPE_PERCENT)
{
$interventionCost = $devis->getTotalInternalHTAfterDiscount() * $society->getInterventionCostPercent() / 100;
}
else if ($society->getInterventionCostType() == Society::INT_COST_TYPE_FIXE)
{
$interventionCost = $society->getInterventionCost();
}
}
$otherCostProvision = $devis->getOtherCostProvision();
$totalCost = $totalCostProducts + $interventionCost + $otherCostProvision;
$profitability = $devis->getTotalInternalHTAfterDiscount() - $totalCost;
if($devis->getTotalInternalHTAfterDiscount() != 0)
{
$profitabilityPercent = $profitability / $devis->getTotalInternalHTAfterDiscount() * 100;
}
else
{
$profitabilityPercent = 0;
}
return array(
"profitability" => $profitability,
"profitabilityPercent" => $profitabilityPercent,
"totalCostProducts" => $totalCostProducts,
"interventionCost" => $interventionCost,
"totalCost" => $totalCost,
);
}
public function suggestEmitters($devis, $access, $mission)
{
$receiver = $devis->getReceiver();
if ($receiver === null)
{
return array();
}
$individual = $receiver->getIndividual();
if ($individual === null)
{
// Store, this one is simple
$devis->setEmitter($receiver);
return array($receiver);
}
$emitters = $access->getStoresForSocietyAsClients($mission->getSociety(), true);
// Also add the emitters of the individual
foreach ($individual->getStores() as $indStore)
{
if (!$emitters->contains($indStore->getClient()))
{
$emitters[] = $indStore->getClient();
}
}
// Add the mission emitter
if ($mission->getEmitter() !== null)
{
// Make sure the select contains the good (default) emitter also
if (!$emitters->contains($mission->getEmitter()))
{
$emitters[] = $mission->getEmitter();
}
// Set emitter
$devis->setEmitter($mission->getEmitter());
}
else
{
// Mission has no emitters
// But does the access have any ? (this is like MissionController::addAction)
if (count($emitters))
{
// Get the first one
$emitter = $emitters[0];
$devis->setEmitter($emitter);
}
else
{
// All hope is lost, set emitter just like receiver
$devis->setEmitter($receiver);
}
}
// Sort emitters
if (count($emitters))
{
$iterator = $emitters->getIterator();
$iterator->uasort(function ($first, $second) {
return (int) $first->getName() < (int) $second->getName() ? 1 : -1;
});
$emitters = $iterator;
}
$emitters = new ArrayCollection(iterator_to_array($emitters));
return $emitters;
}
public function handleFiles($devis)
{
$attachmentRepository = $this->em->getRepository(Attachment::class);
$files = $devis->getFiles();
if ($files !== null)
{
$files = json_decode($files, true);
foreach ($files as $uuid)
{
$attachment = $attachmentRepository->findOneByUuid($uuid);
if ($attachment !== null)
$devis->addAttachment($attachment);
}
}
}
public function devisToArray($devis)
{
$authorId = null;
$authorUsername = null;
$societyId = null;
$societyRef = null;
$societyName = null;
$societyGroupId = null;
$societyGroupRef = null;
$societyGroupName = null;
$receiverIsIndividual = 0;
$receiverId = null;
$receiverName = null;
$receiverLastname = null;
$receiverFirstname = null;
$receiverPhone = null;
$receiverEmail = null;
$receiverAddress = null;
$receiverComplement = null;
$receiverPostalCode = null;
$receiverTown = null;
$receiverCountry = null;
$emitterIsReceiver = 0;
$emitterId = null;
$emitterName = null;
$emitterEmail = null;
$emitterPhone = null;
$emitterAddress = null;
$emitterComplement = null;
$emitterPostalCode = null;
$emitterTown = null;
$emitterCountry = null;
$statusId = null;
$statusValue = null;
$templateId = null;
$templateRef = null;
$templateTitle = null;
$templateDescription = null;
$templateSocietyGroupRef = null;
$templateSocietyGroupName = null;
$interventionAddressAddress = null;
$interventionAddressComplement = null;
$interventionAddressPostalCode = null;
$interventionAddressTown = null;
$interventionAddressCountry = null;
$billingAddressAddress = null;
$billingAddressComplement = null;
$billingAddressPostalCode = null;
$billingAddressTown = null;
$billingAddressCountry = null;
$tva = 20.00;
$secondTva = null;
$thirdTva = null;
$products = array();
$nbAttachments = null;
$devisSimulation = 0;
if ($devis->getAuthor() !== null)
{
$authorId = $devis->getAuthor()->getId();
$authorUsername = $devis->getAuthor()->getUsername();
}
if ($devis->getSociety() !== null)
{
$societyId = $devis->getSociety()->getId();
$societyRef = $devis->getSociety()->getRef();
$societyName = $devis->getSociety()->getName();
if ($devis->getSociety()->getGroup() !== null)
{
$societyGroupId = $devis->getSociety()->getGroup()->getId();
$societyGroupRef = $devis->getSociety()->getGroup()->getRef();
$societyGroupName = $devis->getSociety()->getGroup()->getName();
}
}
if ($devis->getReceiver() !== null)
{
$receiverId = $devis->getReceiver()->getId();
$receiverName = $devis->getReceiver()->getName();
$receiverPhone = $devis->getReceiver()->getPhone();
$receiverEmail = $devis->getReceiver()->getEmail();
if ($devis->getReceiver()->getAddress() !== null)
{
$receiverAddress = $devis->getReceiver()->getAddress()->getAddress();
$receiverPostalCode = $devis->getReceiver()->getAddress()->getPostalCode();
$receiverTown = $devis->getReceiver()->getAddress()->getTown();
$receiverComplement = $devis->getReceiver()->getAddress()->getComplement();
$receiverCountry = $devis->getReceiver()->getAddress()->getCountry();
}
if ($devis->getReceiver()->getIndividual() !== null)
{
$receiverIsIndividual = 1;
$receiverLastname = $devis->getReceiver()->getIndividual()->getLastname();
$receiverFirstname = $devis->getReceiver()->getIndividual()->getFirstname();
}
}
if ($devis->getEmitter() !== null)
{
$emitterId = $devis->getEmitter()->getId();
$emitterName = $devis->getEmitter()->getName();
$emitterEmail = $devis->getEmitter()->getEmail();
$emitterPhone = $devis->getEmitter()->getPhone();
if ($devis->getEmitter()->equals($devis->getReceiver()))
{
$emitterIsReceiver = 1;
}
if ($devis->getEmitter()->getAddress() !== null)
{
$emitterAddress = $devis->getEmitter()->getAddress()->getAddress();
$emitterPostalCode = $devis->getEmitter()->getAddress()->getPostalCode();
$emitterTown = $devis->getEmitter()->getAddress()->getTown();
$emitterComplement = $devis->getEmitter()->getAddress()->getComplement();
$emitterCountry = $devis->getEmitter()->getAddress()->getCountry();
}
}
if ($devis->getTemplate() !== null)
{
$templateId = $devis->getTemplate()->getId();
$templateRef = $devis->getTemplate()->getRef();
$templateTitle = $devis->getTemplate()->getTitle();
$templateDescription = $devis->getTemplate()->getDescription();
if ($devis->getTemplate()->getSocietyGroup() !== null)
{
$templateSocietyGroupRef = $devis->getTemplate()->getSocietyGroup()->getRef();
$templateSocietyGroupName = $devis->getTemplate()->getSocietyGroup()->getName();
}
}
if ($devis->getSimulation())
{
$devisSimulation = 1;
}
if ($devis->getTva() !== null)
{
$tva = $devis->getTva()->getValue();
}
if ($devis->getSecondTva() !== null)
{
$secondTva = $devis->getSecondTva()->getValue();
}
if ($devis->getThirdTva() !== null)
{
$thirdTva = $devis->getThirdTva()->getValue();
}
if ($devis->getInterventionAddress() !== null)
{
$interventionAddressAddress = $devis->getInterventionAddress()->getAddress();
$interventionAddressPostalCode = $devis->getInterventionAddress()->getPostalCode();
$interventionAddressTown = $devis->getInterventionAddress()->getTown();
$interventionAddressComplement = $devis->getInterventionAddress()->getComplement();
$interventionAddressCountry = $devis->getInterventionAddress()->getCountry();
}
if ($devis->getBillingAddress() !== null)
{
$billingAddressAddress = $devis->getBillingAddress()->getAddress();
$billingAddressPostalCode = $devis->getBillingAddress()->getPostalCode();
$billingAddressTown = $devis->getBillingAddress()->getTown();
$billingAddressComplement = $devis->getBillingAddress()->getComplement();
$billingAddressCountry = $devis->getBillingAddress()->getCountry();
}
foreach ($devis->getProducts() as $devisProduct)
{
// Start with the original product data
$product = $devisProduct->getOriginalProduct();
if ($product !== null)
{
$originalProductData = $this->productTools->productToArray($product);
$originalProductId = $product->getId();
}
else
{
$originalProductId = null;
$originalProductData = array();
}
// Add the devis_product data
$devisProductData = $this->productTools->productDevisToArray($devisProduct);
$products[] = array(
"original_product_id" => $originalProductId,
"original_product_data" => $originalProductData,
"devis_product_data" => $devisProductData,
);
}
$nbAttachments = $devis->getAttachments()->count();
return array(
"devis_id" => $devis->getId(),
"devis_ref" => $devis->getRef(),
"devis_simulation" => $devisSimulation,
"author_id" => $authorId,
"author_username" => $authorUsername,
"society_id" => $societyId,
"society_ref" => $societyRef,
"society_name" => $societyName,
"society_group_id" => $societyGroupId,
"society_group_ref" => $societyGroupRef,
"society_group_name" => $societyGroupName,
"receiver_is_individual" => $receiverIsIndividual,
"receiver_id" => $receiverId,
"receiver_name" => $receiverName,
"receiver_lastname" => $receiverLastname,
"receiver_firstname" => $receiverFirstname,
"receiver_phone" => $receiverPhone,
"receiver_email" => $receiverEmail,
"receiver_address" => $receiverAddress,
"receiver_complement" => $receiverComplement,
"receiver_postal_code" => $receiverPostalCode,
"receiver_town" => $receiverTown,
"receiver_country" => $receiverCountry,
"emitter_is_receiver" => $emitterIsReceiver,
"emitter_id" => $emitterId,
"emitter_name" => $emitterName,
"emitter_email" => $emitterEmail,
"emitter_phone" => $emitterPhone,
"emitter_address" => $emitterAddress,
"emitter_complement" => $emitterComplement,
"emitter_postal_code" => $emitterPostalCode,
"emitter_town" => $emitterTown,
"emitter_country" => $emitterCountry,
"status_id" => $statusId,
"status_value" => $statusValue,
"nb_attachments" => $nbAttachments,
"tva" => $tva,
"second_tva" => $this->textTools->encodeForJson($secondTva),
"third_tva" => $this->textTools->encodeForJson($thirdTva),
"devis_title" => $devis->getTitle(),
"devis_info" => $devis->getInfo(),
"devis_ref_order" => $devis->getRefOrder(),
"devis_mention_info" => $this->textTools->encodeForJson($devis->getMentionInfo()),
"devis_annulled_info" => $devis->getAnnulledInfo(),
"devis_percentage_invoicing" => $this->textTools->encodeForJson($devis->getPercentageInvoicing()),
"template_id" => $templateId,
"template_ref" => $templateRef,
"template_title" => $templateTitle,
"template_description" => $templateDescription,
"template_society_group_ref" => $templateSocietyGroupRef,
"template_society_group_name" => $templateSocietyGroupName,
"billing_address_address" => $billingAddressAddress,
"billing_address_complement" => $billingAddressComplement,
"billing_address_postal_code" => $billingAddressPostalCode,
"billing_address_town" => $billingAddressTown,
"billing_address_country" => $billingAddressCountry,
"intervention_address_address" => $interventionAddressAddress,
"intervention_address_complement" => $interventionAddressComplement,
"intervention_address_postal_code" => $interventionAddressPostalCode,
"intervention_address_town" => $interventionAddressTown,
"intervention_address_country" => $interventionAddressCountry,
"total_internal_ht" => $devis->getTotalInternalHT(),
"total_public_ht" => $devis->getTotalPublicHT(),
"total_internal_ht_after_discount" => $devis->getTotalInternalHTAfterDiscount(),
"total_public_ht_after_discount" => $devis->getTotalPublicHTAfterDiscount(),
"total_internal_ttc_after_discount" => $devis->getTotalInternalTTCAfterDiscount(),
"total_public_ttc_after_discount" => $devis->getTotalPublicTTCAfterDiscount(),
"total_internal_discount" => $devis->getTotalInternalDiscount(),
"total_public_discount" => $devis->getTotalPublicDiscount(),
"total_internal_discount_ttc" => $devis->getTotalInternalDiscountTTC(),
"total_public_discount_ttc" => $devis->getTotalPublicDiscountTTC(),
"total_internal_ttc" => $devis->getTotalInternalTTC(),
"total_public_ttc" => $devis->getTotalPublicTTC(),
"total_internal_tva" => $devis->getTotalInternalTVA(),
"total_public_tva" => $devis->getTotalPublicTVA(),
"total_internal_tva_after_discount" => $devis->getTotalInternalTVAAfterDiscount(),
"total_public_tva_after_discount" => $devis->getTotalPublicTVAAfterDiscount(),
"total_internal_second_tva" => $devis->getTotalInternalSecondTVA(),
"total_public_second_tva" => $devis->getTotalPublicSecondTVA(),
"total_internal_second_tva_after_discount" => $devis->getTotalInternalSecondTVAAfterDiscount(),
"total_public_second_tva_after_discount" => $devis->getTotalPublicSecondTVAAfterDiscount(),
"total_internal_third_tva" => $devis->getTotalInternalThirdTVA(),
"total_public_third_tva" => $devis->getTotalPublicThirdTVA(),
"total_internal_third_tva_after_discount" => $devis->getTotalInternalThirdTVAAfterDiscount(),
"total_public_third_tva_after_discount" => $devis->getTotalPublicThirdTVAAfterDiscount(),
"solde_internal" => $devis->getSoldeInternal(),
"solde_public" => $devis->getSoldePublic(),
"products" => $products,
);
}
// Persist, No flush
public function convertSimulation($simulation)
{
// Plan.io Task #4328
$ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
$canApplyEcobonus = $ecobonusIsActive;
if ($simulation->hasEcoBonus())
{
$canApplyEcobonus = true;
}
$devis = new Devis();
$societyGroup = $simulation->getSocietyGroup();
if ($societyGroup === null)
{
// This should not happen
$this->logTools->errorlog('SocietyGroup is null for devis '.$simulation->displayForLog());
return null;
}
// Ref will be generated in the Controller
// Author will be set in the Controller
// Simulations shouldn't have invoices, installments, or be attached to tasks
$devis->setSociety($simulation->getSociety());
$devis->setEmitter($simulation->getEmitter());
// Plan.io Task #4571
// Receiver should be set before Mission when creating a new Devis
// in order to fetch the Zone correctly (at least for the time being)
$devis->setReceiver($simulation->getReceiver());
$devis->setMission($simulation->getMission());
$devis->setTitle($simulation->getTitle());
$devis->setInfo($simulation->getInfo());
$devis->setRefOrder($simulation->getRefOrder());
$devis->setMentionInfo($simulation->getMentionInfo());
$devis->setAnnulledInfo($simulation->getAnnulledInfo());
$devis->setPercentageInvoicing($simulation->getPercentageInvoicing());
$devis->setTemplate($simulation->getTemplate());
$devis->setManager($simulation->getManager());
$defaultStatus = $this->em->getRepository(DevisStatus::class)
->findOneBy(array(
'societyGroup' => $societyGroup,
'inProgress' => 1
));
if ($defaultStatus !== null)
{
$devis->setStatus($defaultStatus);
}
else
{
// This should not happen
$this->logTools->errorlog('InProgress DevisStatus is null');
return null;
}
$devis->setOldStatus($simulation->getStatus());
$devis->setTva($simulation->getTva());
$devis->setSecondTva($simulation->getSecondTva());
$devis->setThirdTva($simulation->getThirdTva());
// Products
foreach ($simulation->getProducts() as $product)
{
$devisProduct = new DevisProduct();
$devisProduct = $this->copyDevisProduct($product, $devisProduct);
if ($canApplyEcobonus && $product->hasEcoBonus())
{
$devisProduct->setEcobonus($product->getEcobonus());
}
$devis->addProduct($devisProduct);
}
// Plan.io Task #4318
if ($canApplyEcobonus)
{
$devis->computeEcobonus();
}
$devis->computeTotals();
// Addresses
$devis->setInterventionAddress($this->addressTools->duplicateAddress($simulation->getInterventionAddress()));
$devis->setBillingAddress($this->addressTools->duplicateAddress($simulation->getBillingAddress()));
// Attachments
foreach ($simulation->getAttachments() as $attachment)
{
// Move attachment
$attachment->setDirectUpload(1);
$attachment->setDevis($devis);
}
if ($devis->getTemplate() !== null)
{
// Plan.io Task #4593
// There can be maximum 2 associated IkeaTemplates
$ikeaTemplates = $this->em->getRepository(IkeaTemplateProduct::class)
->getIkeaTemplatesForPlatformTemplate($devis->getTemplate());
if (count($ikeaTemplates) > 0)
{
$devis->setIkeaTemplate($ikeaTemplates[0]);
if (count($ikeaTemplates) > 1)
{
$devis->setIkeaSecondTemplate($ikeaTemplates[1]);
}
}
}
$devis->setOriginalSimulation($simulation);
$simulation->setResultingDevis($devis);
return $devis;
}
// Persist, No flush
public function convertUnfinishedSimulation($simulation, $mission, $author)
{
// Plan.io Task #4328
$ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
$canApplyEcobonus = $ecobonusIsActive;
if ($simulation->hasEcoBonus())
{
$canApplyEcobonus = true;
}
if ($simulation->getSocietyGroup() === null)
{
// This should not happen
$this->logTools->errorlog('SocietyGroup is null for Devis '.$simulation->displayForLog());
return null;
}
$devis = new Devis();
// We are creating a simulation, not an actual devis
$devis->setSimulation(1);
// Ref will be generated in the Controller
// Simulations shouldn't have invoices, installments, or be attached to tasks
$devis->setAuthor($author);
$devis->setSociety($mission->getSociety());
$devis->setEmitter($mission->getEmitter());
// Plan.io Task #4571
// Receiver should be set before Mission when creating a new Devis
// in order to fetch the Zone correctly (at least for the time being)
$devis->setReceiver($mission->getReceiver());
$devis->setMission($mission);
$devis->setTitle($simulation->getTitle());
$devis->setInfo($simulation->getInfo());
$devis->setRefOrder($simulation->getRefOrder());
$devis->setMentionInfo($simulation->getMentionInfo());
$devis->setAnnulledInfo($simulation->getAnnulledInfo());
$devis->setPercentageInvoicing($simulation->getPercentageInvoicing());
$devis->setTemplate($simulation->getTemplate());
if ($mission->getReceiver() !== null && $mission->getReceiver()->getIndividual() !== null)
$devis->setManager($mission->getReceiver()->getIndividual()->getManager());
if ($devis->getManager() === null)
$devis->setManager($author);
$defaultStatus = $this->em->getRepository(DevisStatus::class)
->findOneBy(array(
'simulation' => 1,
'societyGroup' => $simulation->getSocietyGroup(),
));
if ($defaultStatus !== null)
{
$devis->setStatus($defaultStatus);
}
else
{
// This should not happen
$this->logTools->errorlog('InProgress DevisStatus is null');
return null;
}
$devis->setOldStatus($simulation->getStatus());
$devis->setTva($simulation->getTva());
// Products
foreach ($simulation->getProducts() as $product)
{
$devisProduct = new DevisProduct();
$devisProduct = $this->copyDevisProduct($product, $devisProduct);
// Plan.io Task #4318
if ($canApplyEcobonus && $product->hasEcoBonus())
{
$devisProduct->setEcobonus($product->getEcobonus());
}
$devis->addProduct($devisProduct);
}
// Plan.io Task #4318
if ($canApplyEcobonus)
{
$devis->computeEcobonus();
}
$devis->computeTotals();
// Addresses
if ($mission->getReceiver() !== null)
{
$devis->setInterventionAddress($this->addressTools->duplicateAddress($mission->getReceiver()->getAddress()));
$devis->setBillingAddress($this->addressTools->duplicateAddress($mission->getReceiver()->getBillingAddress()));
}
// Attachments
foreach ($simulation->getAttachments() as $attachment)
{
// Move attachment
$attachment->setDirectUpload(1);
$attachment->setDevis($devis);
}
if ($devis->getTemplate() !== null)
{
// Plan.io Task #4593
// There can be maximum 2 associated IkeaTemplates
$ikeaTemplates = $this->em->getRepository(IkeaTemplateProduct::class)
->getIkeaTemplatesForPlatformTemplate($devis->getTemplate());
if (count($ikeaTemplates) > 0)
{
$devis->setIkeaTemplate($ikeaTemplates[0]);
if (count($ikeaTemplates) > 1)
{
$devis->setIkeaSecondTemplate($ikeaTemplates[1]);
}
}
}
// $devis->setOriginalSimulation($simulation);
// $simulation->setResultingDevis($devis);
// Remove the old simulation
$this->em->remove($simulation);
return $devis;
}
public function copyDevisProduct($from, $to)
{
$to->setRef($from->getRef());
$to->setTitle($from->getTitle());
$to->setDescription($from->getDescription());
$to->setPublicPrice($from->getPublicPrice());
$to->setInternalPrice($from->getInternalPrice());
$to->setQuantity($from->getQuantity());
$to->setPercentage($from->getPercentage());
$to->setInvoiced($from->getInvoiced());
$to->setInvoicedPercentage($from->getInvoicedPercentage());
$to->setTotalPublicHT($from->getTotalPublicHT());
$to->setTotalInternalHT($from->getTotalInternalHT());
$to->setTotalPublicTTC($from->getTotalPublicTTC());
$to->setTotalInternalTTC($from->getTotalInternalTTC());
$to->setDiscount($from->getDiscount());
$to->setReadonly($from->getReadonly());
$to->setSamePrice($from->getSamePrice());
$to->setOptionNoPrice($from->getOptionNoPrice());
$to->setUnit($from->getUnit());
$to->setProduct($from->getProduct());
$to->setQuestion($from->getQuestion());
$to->setOriginalProduct($from->getOriginalProduct());
$to->setPosition($from->getPosition());
$to->setTva($from->getTva());
$to->setOptionNoPrice($from->getOptionNoPrice());
// Plan.io Task #4561
$to->setLinearMetersLower($from->getLinearMetersLower(true));
$to->setLinearMetersUpper($from->getLinearMetersUpper(true));
$to->setLinearMetersColumn($from->getLinearMetersColumn(true));
$to->setLinearMetersIsland($from->getLinearMetersIsland(true));
$to->setLinearMetersIntegratedAppliance($from->getLinearMetersIntegratedAppliance(true));
return $to;
}
// Plan.io Task #4624 : This is also called from NotebookTools :: updateNotebook_additionalWork
public function handleProductsFromForm($devis, $formProducts, $editMode = false)
{
// Plan.io Task #4328
$ecobonusIsActive = $this->security->isGranted('eco_bonus_is_active');
$canApplyEcobonus = $ecobonusIsActive;
// Plan.io Task #3800
$titlePositions = array();
$resultProducts = array();
if ($editMode)
{
// Plan.io Task #4328
if ($devis->hasEcoBonus())
{
$canApplyEcobonus = true;
}
foreach ($devis->getProducts() as $product)
{
// Plan.io Task #3626
// We need to remove the ref of this product from all InvoiceProducts it may reference
// This should not pose a problem since
// - if the invoice is stil valid, then we cannot edit corresponding devis, thus we should not be here
// - if the invoice has been annulled by a credit invoice, then the invoice is no longer editable
$invoiceProducts = $this->em->getRepository(InvoiceProduct::class)->findByDevisProduct($product);
foreach ($invoiceProducts as $iProduct)
{
$iProduct->setDevisProduct(null);
}
$devis->removeProduct($product);
$this->em->remove($product);
}
// Plan.io Task #4328
// Need to reset ecobonus (Don't check if module is active here)
$devis->setEcoBonus(null);
// Plan.io Task #3800
// Get the original positions of the titles before updating the Devis
// Set them as keys of the array
// so we can insert the products on the missing array keys in order of arrival
foreach ($devis->getTitles() as $key => $title)
{
$titlePositions[$title->getPosition()] = $title->getPosition();
}
}
if (empty($formProducts))
return null;
$formProducts = json_decode($formProducts, true);
if (empty($formProducts))
return null;
$productsRepository = $this->em->getRepository(Product::class);
$unitRepository = $this->em->getRepository(Unit::class);
// The discount, if any, should be last
$discount = null;
$ecoBonusProduct = null;
// Take the position into account
foreach($formProducts as $product)
{
if (array_key_exists('id', $product) &&
array_key_exists('ref', $product) &&
array_key_exists('quantity', $product) &&
array_key_exists('internal_price', $product) &&
array_key_exists('public_price', $product) &&
array_key_exists('title', $product) &&
array_key_exists('info', $product) &&
array_key_exists('unit', $product))
{
$product_id = $product['id'];
$product_ref = $product['ref'];
$product_qt = $product['quantity'];
$product_internal_price = $product['internal_price'];
$product_public_price = $product['public_price'];
$product_title = $product['title'];
$product_info = $product['info'];
$product_unit = $product['unit'];
$product_ecobonus = 0;
if (array_key_exists('ecobonus', $product))
{
$product_ecobonus = floatval($product['ecobonus']);
}
// Handle free product
if (intval($product_id) == -1)
{
$product = $productsRepository->findOneBy(array(
'societyGroup' => $devis->getSocietyGroup(),
'free' => 1,
));
}
else
{
$product = $productsRepository->findOneById($product_id);
}
$product_unit = $unitRepository->findOneById($product_unit);
if ($product === null)
continue;
if ($product_qt <= 0)
continue;
if ($product_internal_price === null)
$product_internal_price = 0;
if ($product_public_price === null)
$product_public_price = 0;
// Discount ?
if ($product->isDiscount() && $discount !== null)
{
// This should not happen
// Only one discount is allowed for a devis
continue;
}
// Plan.io Task #4328
if ($canApplyEcobonus && $ecoBonusProduct !== null && !empty($product_ecobonus))
{
// This should not happen
// Only one EcoProduct is allowed for a devis
continue;
}
// Product
$dp = new DevisProduct();
// Plan.io Task #3800
// Save for position
$resultProducts[] = $dp;
$dp->setOriginalProduct($product);
if ($editMode)
$dp->setRef($product_ref);
else
$dp->setRef($product->getRef());
$dp->setTitle($product->getTitle());
$dp->setDescription($product->getDescription());
$dp->setOptionNoPrice($product->getOptionNoPrice());
$dp->setTva($product->getTva());
$dp->setRefCost($product->getRefCost());
// Check if the product can be edited, if not use product value
if ($product->getReadonly())
{
// Not editable
// $dp->setPublicPrice($product->getPublicPrice());
// $dp->setInternalPrice($product->getInternalPrice());
// Plan.io Task #3345
$dp->setPublicPrice($product_public_price);
$dp->setInternalPrice($product_internal_price);
$dp->setTitle($product->getTitle());
$dp->setDescription($product->getDescription());
}
else
{
// Editable
// Set title
$dp->setTitle($product_title);
$dp->setDescription($product_info);
// Set price, taking into account 'samePrice' constraint
if ($product->getSamePrice())
{
// Both prices must be the same,
// Get the value given by the user
$dp->setPublicPrice($product_internal_price);
$dp->setInternalPrice($product_internal_price);
}
else
{
// Prices can be different
$dp->setPublicPrice($product_public_price);
$dp->setInternalPrice($product_internal_price);
}
}
if ($product_unit === null)
{
$product_unit = $product->getUnit();
}
$dp->setUnit($product_unit);
$dp->setQuantity($product_qt);
$dp->setQuestion($product->getQuestion());
$dp->setReadonly($product->getReadonly());
$dp->setSamePrice($product->getSamePrice());
// Plan.io Task #4561
$dp->setLinearMetersLower($product->getLinearMetersLower(true));
$dp->setLinearMetersUpper($product->getLinearMetersUpper(true));
// Plan.io Task #4592
$dp->setLinearMetersColumn($product->getLinearMetersColumn(true));
$dp->setLinearMetersIsland($product->getLinearMetersIsland(true));
$dp->setLinearMetersIntegratedAppliance($product->getLinearMetersIntegratedAppliance(true));
// Plan.io Task #4328
if ($canApplyEcobonus && !empty($product_ecobonus))
{
$dp->setEcoBonus($product_ecobonus);
$ecoBonusProduct = $dp;
}
// Plan.io Task #3343
// Only consider internal prices if
// - devis/mission is JCAF
// - devis.emitter = devis.receiver
if ($devis->mustHaveEqualPrices())
{
// Do some logging
if ($dp->getPublicPrice() != $dp->getInternalPrice())
{
$this->logTools->errorlog("PublicPrice # InternalPrice where it should not be for devis ".$devis->displayForLog());
}
// Only keep the InternalPrice
$dp->setPublicPrice($dp->getInternalPrice());
$dp->setSamePrice(1);
}
if ($product->isDiscount())
{
// The discount, if any, should be last
$dp->setDiscount(1);
$discount = $dp;
}
else
{
$devis->addProduct($dp);
}
}
// Now add the discount, if any
if ($discount !== null)
{
$devis->addProduct($discount);
}
}
// Plan.io Task #4328
if ($canApplyEcobonus)
{
$devis->computeEcobonus();
}
// Plan.io Task #3800
// Set positions
// Insert the products on the missing array keys in order of arrival
// The existing keys represent the existing titles
$nb = count($titlePositions) + count($resultProducts);
$key = 0;
for ($pos = 1 ; $pos <= $nb ; $pos++)
{
if (array_key_exists($pos, $titlePositions))
{
}
else
{
$titlePositions[$pos] = $resultProducts[$key];
$key ++;
}
}
foreach ($titlePositions as $pos => $item)
{
if ($item instanceof DevisProduct)
{
$item->setPosition($pos);
}
}
return true;
}
// No Flush, Persist only
public function updateRefOrders($data)
{
if (empty($data))
return false;
$devisRep = $this->em->getRepository(Devis::class);
// Handle data
foreach ($data as $devisItem)
{
$id = $devisItem[0];
$value = $devisItem[1];
// Get the devis
$devis = $devisRep->findOneById($id);
if ($devis === null)
continue;
$oldValue = $devis->getRefOrder();
if ($oldValue != $value)
{
// Update refOrder
$devis->setRefOrder($value);
$this->em->persist($devis);
$devis->setLoggingData(["info" => $this->translator->trans('updated_via_task')]);
}
}
return true;
}
// No Flush, Persist only
public function updateRefOrder($devis, $refOrder)
{
if ($devis === null)
return false;
$backTrace = debug_backtrace()[0];
$method = $backTrace['function'];
$class = $backTrace['class'];
$trace = $class . "::" . $method;
$trace = str_replace("\\", "::", $trace);
$oldValue = $devis->getRefOrder();
if ($oldValue != $refOrder)
{
// Update refOrder
$devis->setRefOrder($refOrder);
$this->em->persist($devis);
// Plan.io Task #3922
$devis->setLoggingData(["info" => $this->translator->trans('devis_edit_ref_order_via_invoice')]);
}
return true;
}
// Plan.io Task #3621
// No Flush, Persist only
public function updateIkeaOrderNumber($devis, $refOrder)
{
if ($devis === null)
return false;
$oldValue = $devis->getIkeaOrderNumber();
if ($oldValue != $refOrder)
{
// Update refOrder
$devis->setIkeaOrderNumber($refOrder);
$this->em->persist($devis);
// Plan.io Task #3922
$devis->setLoggingData(["info" => $this->translator->trans('devis_edit_ref_order_via_invoice')]);
}
return true;
}
// No Flush
public function updateStatusConsideringInvoicing($args)
{
// Get all the arguments
// Devis is mandatory
if (array_key_exists("devis", $args)) $devis = $args["devis"];
else return null;
if (array_key_exists("info", $args)) $info = $args["info"];
else $info = null;
if ($devis->isAnnulled())
return false;
// Plan.io Task #4326
// Just in case
if (array_key_exists("invoice", $args))
{
$invoice = $args["invoice"];
if ($invoice->isDraft())
{
// Nothing to do here => Bye bye
return false;
}
}
// $this->logTools->ploopLog("[#4326][".$devis->getId()."][".$devis->getRef()."] DevisTools :: updateStatusConsideringInvoicing");
$invoicing = $this->checkInvoicing($devis);
// If Empty or Not Invoiced, do nothing
if ($invoicing["empty"] || $invoicing["not_invoiced"])
return false;
$status = $invoicing["status"];
if ($status !== null)
{
// $oldStatus = $devis->getStatus()->getValue();
// $newStatus = $status->getValue();
$devis->setStatus($status);
return true;
}
return false;
}
// Plan.io Task #3898.Zones.ZoneProducts.iTasks
// Rekto : InvoiceTools :: updateDevisTaskStatus
// Plan.io Task #4326 : Added $invoice param
public function updateDevisTaskStatus(Devis $devis, Invoice $invoice)
{
// $this->logTools->ploopLog("[#4326][".$devis->getId()."][".$devis->getRef()."] DevisTools :: updateDevisTaskStatus");
// Code should be applied only for Rekto Group
if ($devis->getSocietyGroup() === null || $devis->getSocietyGroup()->isRekto() === false)
{
return false;
}
if ($devis->isAnnulled())
return false;
// Only change task status for devis which are totally invoiced
if (!$devis->isInvoiced())
return false;
// Plan.io Task #4326
// Just in case
if ($invoice->isDraft())
{
// Nothing to do here => Bye bye
return false;
}
$taskStatusRepository = $this->em->getRepository(TaskStatus::class);
$invoicedFinished = $taskStatusRepository->findOneBy(array(
'societyGroup' => $devis->getSocietyGroup(),
'iTaskAuto' => 1,
'code' => TaskStatus::TASK_STATUS_INVOICED_FINISHED,
));
if ($invoicedFinished === null)
{
return false;
}
$newStatus = $invoicedFinished->getValue();
foreach ($devis->getTasks() as $task)
{
$tochange = true;
// check if task has another not invoiced devis
foreach($task->getDevisGroup() as $taskDevis)
{
if (!$devis->equals($taskDevis) && !$taskDevis->getStatus()->isInvoiced())
{
$tochange = false;
}
}
if ($tochange === false)
{
continue;
}
$oldStatus = $task->getStatus()->getValue();
$task->setStatus($invoicedFinished);
// Plan.io Task #3865
// This one does not flush
$this->advancedNotificationTools->updateAdvancedNotificationForTask($task);
}
return true;
}
// Plan.io Task #3898.Zones.ZoneProducts.iTasks
// Rekto : DevisTools :: handleStatusChangeForTask
public function handleDevisStatusChangeForTask(Task $task)
{
$output = [
'status' => false,
'devis_updated' => [],
];
$devisUpdated = [];
if (!$this->security->isGranted('devis_is_active'))
{
// Module not active, exit
return $output;
}
$societyGroup = $task->getSocietyGroup();
if ($societyGroup === null)
{
// Should never happen
$this->logTools->errorLog('SocietyGroup is null for task : ' . $task->displayForLog());
return $output;
}
$devisStatusRepository = $this->em->getRepository(DevisStatus::class);
$inProgressStatus = $devisStatusRepository->findOneBy(array(
'societyGroup' => $societyGroup,
'inProgress' => 1,
));
$plannedStatus = $devisStatusRepository->findOneBy(array(
'societyGroup' => $societyGroup,
'planned' => 1,
));
if ($inProgressStatus === null || $plannedStatus === null)
{
// Should never happen
$this->logTools->errorLog('Status planned or inprogress not found in societyGroup : ' . $societyGroup->displayForLog());
return $output;
}
$devisGroup = $task->getDevisGroup();
if ($devisGroup->isDirty() == false)
{
// $this->logTools->ploopLog('Is not dirty');
$output['status'] = true;
return $output;
}
// Get trace for log
$backTrace = debug_backtrace()[0];
$method = $backTrace['function'];
$class = $backTrace['class'];
$trace = $class . "::" . $method;
$trace = str_replace("\\", "::", $trace);
// Handle add case : DevisStatus :: planned
foreach ($devisGroup as $key => $devis)
{
$isOldDevis = false;
foreach ($devisGroup->getSnapshot() as $snapshotDevis)
{
if ($devis->equals($snapshotDevis))
{
$isOldDevis = true;
break;
}
}
if ($isOldDevis)
{
// Nothing to do here
continue;
}
if ($devis->isInProgress() === false)
{
// Nothing to do here
continue;
}
// Plan.io Task #3922
$devis->setLoggingData(["info" => "devis_auto_edit_status_added_to_task"]);
$devis->setStatus($plannedStatus);
$devisUpdated[] = $devis;
}
// Handle devis removed
foreach ($task->getDevisGroup()->getSnapshot() as $snapshotDevis)
{
$isOldDevis = false;
foreach ($devisGroup as $devis)
{
if ($snapshotDevis->equals($devis))
{
$isOldDevis = true;
break;
}
}
if ($isOldDevis)
{
// Nothing to do here
continue;
}
$devis = $snapshotDevis;
// Only do this if devis is 'planned' and not in another task (#2639)
// Here getTasks should be 0 if devis had a single task
if ($devis->isPlanned() === false || sizeof($devis->getTasks()) > 0)
{
// Nothing to do here
continue;
}
// Devis has been removed,
// Revert it to old status, if available
// Revert to default status if not
$devisStatus = $inProgressStatus;
if ($devis->getOldStatus() !== null && $devis->getOldStatus()->isPlanned() === false)
{
$devisStatus = $devis->getOldStatus();
}
// Plan.io Task #3922
$devis->setLoggingData(["info" => "devis_auto_edit_status_removed_from_task"]);
$devis->setStatus($devisStatus);
$devisUpdated[] = $devis;
}
$output['status'] = true;
$output['devis_updated'] = $devisUpdated;
return $output;
}
// This checks to see if the devis
// is not invoiced, partially invoiced or totally invoiced
// based on the state of its products
public function checkInvoicing($devis)
{
if ($devis->getProducts()->count() < 1)
return array(
"empty" => true,
"not_invoiced" => false,
"partially_invoiced" => false,
"invoiced" => false,
"status" => null,
);
$statusRepository = $this->em->getRepository(DevisStatus::class);
$statusPartiallyInvoiced = $statusRepository
->findOneBy(array(
'partiallyInvoiced' => 1,
'societyGroup' => $devis->getSocietyGroup(),
));
$statusInvoiced = $statusRepository
->findOneBy(array(
'invoiced' => 1,
'societyGroup' => $devis->getSocietyGroup(),
));
$totalQuantity = 0;
$totalInvoiced = 0;
foreach($devis->getProducts() as $product)
{
if ($product->isNotDiscount())
{
$totalQuantity += $product->getQuantity();
$totalInvoiced += $product->getInvoiced();
}
}
// Not invoiced
if ($totalInvoiced == 0)
{
return array(
"empty" => false,
"not_invoiced" => true,
"partially_invoiced" => false,
"invoiced" => false,
"status" => null,
);
}
// Partially invoiced
if ($totalInvoiced < $totalQuantity)
{
return array(
"empty" => false,
"not_invoiced" => false,
"partially_invoiced" => true,
"invoiced" => false,
"invoiced" => false,
"status" => $statusPartiallyInvoiced,
);
}
// If we are here it means that the devis has at least one product
// and the total Invoiced is equal to the total Quantity
// Plan.io Task #4326 : Or higher :)
return array(
"empty" => false,
"not_invoiced" => false,
"partially_invoiced" => false,
"invoiced" => true,
"status" => $statusInvoiced,
);
}
// Plan.io Task #4353
// Lists invoices considered annulled (excluding itself) linked to the same devis in the invoice view.
public function getListingInvoiceWithCredit($devis, $invoice)
{
$invoicesAnnulled = [];
$devisInvoices = $devis->getInvoicesOnly();
foreach ($devisInvoices as $devisInvoice)
{
if ($invoice === $devisInvoice)
{
break;
}
// We are looking to see if a credit note is linked to this invoice
$devisCredit = $this->em->getRepository(Invoice::class)->findOneBy(array(
'creditInvoice' => $devisInvoice,
'draft' => 0,
));
// If a credit note is found, the invoice is considered cancelled
if ($devisCredit !== null)
{
$invoicesAnnulled[] = $devisInvoice;
}
}
return $invoicesAnnulled;
}
public function handlePositionForTitleCreation($title, $elements)
{
foreach($elements as $el)
{
if($el->getPosition() !== null)
{
$el->setPosition($el->getPosition() + 1);
}
}
$title->setPosition(1);
}
public function updatePositionTitleDelete($oldPos, $products, $titles)
{
// Sort by Position
$mergedProducts = array_merge($products->toArray(), $titles->toArray());
if (count($mergedProducts))
{
$iterator = $mergedProducts;
uasort($mergedProducts,function ($first, $second)
{
return (int) $first->getPosition() > (int) $second->getPosition() ? 1 : -1;
});
}
// Apply -1 when old position is inferior at current position
foreach ($mergedProducts as $mp)
{
if($oldPos < $mp->getPosition())
{
$newPosition = $mp->getPosition() - 1;
$mp->setPosition($newPosition);
$this->em->persist($mp);
}
}
}
}