src/Entity/Freelancer.php line 27

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use App\Entity\Enum\BusinessModelEnum;
  4. use App\Entity\Enum\FreelancerBusinessSoftwareEnum;
  5. use App\Entity\Enum\OnboardingStatusEnum;
  6. use App\Entity\Traits\CreditOwnerInterface;
  7. use App\Entity\Traits\CreditOwnerTrait;
  8. use DateTime;
  9. use DateTimeInterface;
  10. use Doctrine\Common\Collections\ArrayCollection;
  11. use Doctrine\Common\Collections\Collection;
  12. use Doctrine\ORM\Mapping as ORM;
  13. use Symfony\Component\HttpFoundation\File\File;
  14. use Symfony\Component\Validator\Constraints as Assert;
  15. use Symfony\Component\Validator\Context\ExecutionContextInterface;
  16. use Vich\UploaderBundle\Mapping\Annotation as Vich;
  17. use Gedmo\Mapping\Annotation as Gedmo;
  18. /**
  19.  * @ORM\Entity(repositoryClass="App\Repository\FreelancerRepository")
  20.  * @ORM\EntityListeners({"App\Listener\FreelancerListener"})
  21.  * @Gedmo\Loggable(logEntryClass="App\Entity\LogEntry")
  22.  * @Vich\Uploadable
  23.  */
  24. class Freelancer extends Administrator implements CreditOwnerInterface
  25. {
  26.     use CreditOwnerTrait;
  27.     public const CREDIT_ALMOST_DOWN 2;
  28.     private const PAYASYOUGO_CAPPING_DEFAULT 5;
  29.     /**
  30.      * Fake deadline used to identify freelance who have their store indexable till they reach 0 credit
  31.      */
  32.     public const BUSINESS_MODEL_FAKE_DEADLINE '2030-01-01 00:00:00';
  33.     /**
  34.      * Deadline relacing the 2 months previously used before passing businessModel from 'credit' to 'none'
  35.      */
  36.     public const BUSINESS_FORCED_DEADLINE '2025-05-20 00:00:00';
  37.     /**
  38.      * @ORM\Column(type="string", length=25, nullable=true)
  39.      */
  40.     private $vatNumber;
  41.     /**
  42.      * @Assert\NotBlank()
  43.      * @Assert\Regex(pattern="/^\d{14}$/", message="Le numéro de SIRET n'est pas valide.")
  44.      * @ORM\Column(type="string", length=25)
  45.      */
  46.     private $siretNumber;
  47.     /**
  48.      * @Assert\Regex(pattern="/^(?:\+33|0)\s*[1-9](?:[\s.-]*\d{2}){4}$/", message="Le numéro de téléphone n'est pas valide.")
  49.      * @ORM\Column(type="string", length=255, nullable=true)
  50.      */
  51.     private $phoneNumberSmsNotification;
  52.     /**
  53.      * @ORM\Column(type="string", length=255, nullable=true)
  54.      */
  55.     private $emailNotification;
  56.     /**
  57.      * @var boolean
  58.      * @ORM\Column(type="boolean")
  59.      */
  60.     private $isSmsNotification;
  61.     /**
  62.      * @var boolean
  63.      * @ORM\Column(type="boolean")
  64.      */
  65.     private $isEmailNotification;
  66.     /**
  67.      * specify if this freelancer has already been an premium member (even if nbCredit is null)
  68.      * @var boolean
  69.      * @ORM\Column(type="boolean", options={"default" : false})
  70.      */
  71.     private $hasBeenPremium;
  72.     /**
  73.      * @ORM\Column(type="datetime", nullable=true)
  74.      */
  75.     private $soldOutAt;
  76.     /**
  77.      * specify if this freelancer is active and can receive prospect
  78.      * @var boolean
  79.      * @ORM\Column(type="boolean", options={"default" : true})
  80.      */
  81.     private $isEnabled 1;
  82.     /**
  83.      * @ORM\OneToMany(targetEntity="App\Entity\Order", mappedBy="freelancer", cascade={"remove"})
  84.      */
  85.     private $orders;
  86.     /**
  87.      * @ORM\OneToMany(targetEntity=FreelancerAssistant::class, mappedBy="freelancer")
  88.      */
  89.     private $freelancerAssistants;
  90.     /**
  91.      * @ORM\Column(type="datetime", nullable=true)
  92.      */
  93.     private $updatedAt;
  94.     /**
  95.      * @ORM\Column(type="string", length=255, nullable=true)
  96.      */
  97.     private $picturePath;
  98.     /**
  99.      * @assert\File( mimeTypes = {"image/jpeg", "image/png", "image/gif", "image/jpg","image/webp"})
  100.      * @Vich\UploadableField(mapping="freelancer_images", fileNameProperty="picturePath")
  101.      * @var File
  102.      */
  103.     private $pictureFile;
  104.     /**
  105.      * @ORM\Column(type="string", length=255, nullable=true)
  106.      */
  107.     private $logoPath;
  108.     /**
  109.      * @assert\File( mimeTypes = {"image/jpeg", "image/png", "image/gif", "image/jpg","image/webp"})
  110.      * @Vich\UploadableField(mapping="freelancer_logo", fileNameProperty="logoPath")
  111.      * @var File
  112.      */
  113.     private $logoFile;
  114.     /**
  115.      * @ORM\Column(type="integer")
  116.      */
  117.     private $nbPendingLead;
  118.     /**
  119.      * @ORM\Column(type="integer", nullable=true)
  120.      */
  121.     private $nbCreditSms;
  122.     /**
  123.      * @ORM\OneToMany(targetEntity=FreelancerComment::class, mappedBy="freelancer", orphanRemoval=true)
  124.      */
  125.     private $comments;
  126.     /**
  127.      * @ORM\Column(type="integer", nullable=true)
  128.      */
  129.     private $businessSoftware;
  130.     /**
  131.      * @ORM\Column(type="integer", nullable=true)
  132.      * @Assert\Range(
  133.      *      min = 1,
  134.      *      max = 99,
  135.      *      notInRangeMessage = "Le nombre de contact doit être entre {{ min }} et {{ max }}",
  136.      * )
  137.      */
  138.     private ?int $payAsYouGoCapping self::PAYASYOUGO_CAPPING_DEFAULT;
  139.     /**
  140.      * @ORM\OneToMany(targetEntity=CreditCost::class, mappedBy="administrator")
  141.      */
  142.     private Collection $creditCosts;
  143.     /**
  144.      * @ORM\Column(type="string", length=255, nullable=true)
  145.      */
  146.     private string $onboardingStatus OnboardingStatusEnum::IN_PROGRESS;
  147.     /**
  148.      * @ORM\Column(type="integer", name="stat_nb_credit_purchased")
  149.      */
  150.     private int $nbCreditPurchased 0;
  151.     /**
  152.      * @ORM\Column(type="integer", name="stat_nb_lead_delivered")
  153.      */
  154.     private int $nbLeadDelivered 0;
  155.     /**
  156.      * @ORM\Column(type="integer", name="stat_nb_sales")
  157.      */
  158.     private int $nbSales 0;
  159.     /**
  160.      * @ORM\Column(type="boolean", options={"default" : false})
  161.      */
  162.     private bool $schedulingAppointment false;
  163.     public function __construct()
  164.     {
  165.         parent::__construct();
  166.         $this->isEmailNotification true;
  167.         $this->isSmsNotification true;
  168.         $this->hasBeenPremium false;
  169.         $this->orders = new ArrayCollection();
  170.         $this->freelancerAssistants = new ArrayCollection();
  171.         $this->comments = new ArrayCollection();
  172.         $this->setBusinessModel(BusinessModelEnum::NONE);
  173.     }
  174.     public function setBusinessModel(int $businessModel): void
  175.     {
  176.         parent::setBusinessModel($businessModel);
  177.         $this->updateCustomerStatus();
  178.     }
  179.     /**
  180.      * when nbCredit or nbPendingLead is updated, we check if this freelancer is still a customer
  181.      * - freelance must be enabled
  182.      * AND
  183.      *      - freelance must have enough credit to receive leads
  184.      *      OR
  185.      *      - freelance must have opted-in for "pay as you go" credit && have less than 2 pending credit costs
  186.      */
  187.     public function updateCustomerStatus()
  188.     {
  189.         if ($this->isEnabled() && (($this->nbCredit $this->nbPendingLead) > || ($this->isPayAsYouGo() && !$this->isPayAsYouGoThresholdReached()))) {
  190.             $this->setIsCustomer(true);
  191.         } else {
  192.             $this->setIsCustomer(false);
  193.         }
  194.     }
  195.     /**
  196.      * @return bool
  197.      */
  198.     public function isEnabled(): bool
  199.     {
  200.         return $this->isEnabled;
  201.     }
  202.     public function isPayAsYouGoThresholdReached(): bool
  203.     {
  204.         if (is_null($this->getPayAsYouGoCapping())) {
  205.             return false;
  206.         }
  207.         return $this->getNbPendingCreditCosts() + $this->getNbPendingLead() >= $this->getPayAsYouGoCapping();
  208.     }
  209.     public function getPayAsYouGoCapping(): ?int
  210.     {
  211.         if (is_null($this->payAsYouGoCapping)) {
  212.             return self::PAYASYOUGO_CAPPING_DEFAULT;
  213.         }
  214.         return $this->payAsYouGoCapping;
  215.     }
  216.     public function setPayAsYouGoCapping(?int $payAsYouGoCapping): void
  217.     {
  218.         $this->payAsYouGoCapping $payAsYouGoCapping;
  219.         $this->updateCustomerStatus();
  220.     }
  221.     public function getNbPendingCreditCosts(): int
  222.     {
  223.         return $this->getPendingCreditCosts()->count();
  224.     }
  225.     public function getPendingCreditCosts(): Collection
  226.     {
  227.         return $this->creditCosts->filter(function (CreditCost $creditCost) {
  228.             return $creditCost->isPending();
  229.         });
  230.     }
  231.     public function getNbPendingLead(): ?int
  232.     {
  233.         return $this->nbPendingLead;
  234.     }
  235.     public function setNbPendingLead(int $nbPendingLead): self
  236.     {
  237.         $this->nbPendingLead $nbPendingLead;
  238.         $this->updateCustomerStatus();
  239.         return $this;
  240.     }
  241.     /**
  242.      * @Assert\Callback()
  243.      */
  244.     public function validate(ExecutionContextInterface $context$payload)
  245.     {
  246.         //if freelancer has a businessModel, he must have a paiement method
  247.         if ($this->getBusinessModel() !== BusinessModelEnum::NONE) {
  248.             if (!$this->getUser() ||
  249.                 $this->getUser()->getPaymentMethods()->isEmpty()) {
  250.                 $context->buildViolation('Au moins un moyen de paiement doit être connu pour pouvoir choisir un modèle économique.')
  251.                     ->atPath('businessModel')
  252.                     ->addViolation();;
  253.             }
  254.         }
  255.     }
  256.     /**
  257.      * @return mixed
  258.      */
  259.     public function getUpdatedAt(): ?DateTimeInterface
  260.     {
  261.         return $this->updatedAt;
  262.     }
  263.     /**
  264.      * @param mixed $updatedAt
  265.      */
  266.     public function setUpdatedAt(?DateTimeInterface $updatedAt): self
  267.     {
  268.         $this->updatedAt $updatedAt;
  269.         return $this;
  270.     }
  271.     public function getPicturePath(): ?string
  272.     {
  273.         return $this->picturePath;
  274.     }
  275.     public function setPicturePath(?string $picturePath)
  276.     {
  277.         $this->picturePath $picturePath;
  278.         return $this;
  279.     }
  280.     public function hasPicture()
  281.     {
  282.         return !is_null($this->picturePath);
  283.     }
  284.     public function getPictureFile()
  285.     {
  286.         return $this->pictureFile;
  287.     }
  288.     public function setPictureFile(File $picturePath null)
  289.     {
  290.         $this->pictureFile $picturePath;
  291.         if ($picturePath) {
  292.             $this->updatedAt = new DateTime('now');
  293.         }
  294.     }
  295.     /**
  296.      * Représente l'entité par son nom et son type (admin ou freelance)
  297.      * @return string
  298.      */
  299.     public function getLabelWithType()
  300.     {
  301.         return $this->getName() . ' [Indépendant]';
  302.     }
  303.     public function getNameAndEmailUser()
  304.     {
  305.         if ($this->getUser() !== null) {
  306.             return strtoupper($this->getName()) . ' [' $this->getUser()->getEmail() . ']';
  307.         } else {
  308.             return $this->getName();
  309.         }
  310.     }
  311.     public function getId(): ?int
  312.     {
  313.         return $this->id;
  314.     }
  315.     public function getVatNumber(): ?string
  316.     {
  317.         return $this->vatNumber;
  318.     }
  319.     public function setVatNumber(string $vatNumber): self
  320.     {
  321.         $this->vatNumber $vatNumber;
  322.         return $this;
  323.     }
  324.     public function setNbCredit(int $nbCredit): self
  325.     {
  326.         $this->nbCredit $nbCredit;
  327.         $this->updateCustomerStatus();
  328.         return $this;
  329.     }
  330.     public function getSiretNumber(): ?string
  331.     {
  332.         return $this->siretNumber;
  333.     }
  334.     public function setSiretNumber(string $siretNumber): self
  335.     {
  336.         $siretNumber str_replace(' '''$siretNumber);
  337.         $this->siretNumber $siretNumber;
  338.         return $this;
  339.     }
  340.     // Returns an array with every mail addresses for a store
  341.     public function mustBeNotifiedBySms(): ?bool
  342.     {
  343.         return $this->getIsSmsNotification() && $this->getPhoneNumberSmsNotification();
  344.     }
  345.     public function getIsSmsNotification(): ?bool
  346.     {
  347.         return $this->isSmsNotification;
  348.     }
  349.     public function setIsSmsNotification(bool $isSmsNotification): self
  350.     {
  351.         $this->isSmsNotification $isSmsNotification;
  352.         return $this;
  353.     }
  354.     public function getPhoneNumberSmsNotification(): ?string
  355.     {
  356.         return $this->phoneNumberSmsNotification;
  357.     }
  358.     public function setPhoneNumberSmsNotification(?string $phoneNumberSmsNotification): self
  359.     {
  360.         $this->phoneNumberSmsNotification $phoneNumberSmsNotification;
  361.         return $this;
  362.     }
  363.     /**
  364.      * @return string
  365.      * @deprecated use Formatter->phoneNumberInternationalFormat instead
  366.      */
  367.     public function getPhoneNumberSmsNotificationFormatted()
  368.     {
  369.         return wordwrap($this->getPhoneNumberSmsNotification(), 2' 'true);
  370.     }
  371.     public function mustBeNotifiedByEmail(): ?bool
  372.     {
  373.         return $this->getIsEmailNotification() && !empty($this->getEmailNotification());
  374.     }
  375.     public function getIsEmailNotification(): ?bool
  376.     {
  377.         return $this->isEmailNotification;
  378.     }
  379.     public function setIsEmailNotification(bool $isEmailNotification): self
  380.     {
  381.         $this->isEmailNotification $isEmailNotification;
  382.         return $this;
  383.     }
  384.     public function getEmailNotification(): ?string
  385.     {
  386.         return $this->emailNotification;
  387.     }
  388.     public function setEmailNotification(?string $emailNotification): self
  389.     {
  390.         $this->emailNotification $emailNotification;
  391.         return $this;
  392.     }
  393.     public function getEmailList(): array
  394.     {
  395.         // Optim: use constant for delimiter?
  396.         return explode(";"$this->getEmailNotification());
  397.     }
  398.     /**
  399.      * @return bool
  400.      */
  401.     public function isHasBeenPremium(): bool
  402.     {
  403.         return $this->hasBeenPremium;
  404.     }
  405.     public function getHasBeenPremium(): ?bool
  406.     {
  407.         return $this->hasBeenPremium;
  408.     }
  409.     /**
  410.      * @param bool $hasBeenPremium
  411.      */
  412.     public function setHasBeenPremium(bool $hasBeenPremium): void
  413.     {
  414.         $this->hasBeenPremium $hasBeenPremium;
  415.     }
  416.     /**
  417.      * @return Collection|Order[]
  418.      */
  419.     public function getOrders(): Collection
  420.     {
  421.         return $this->orders;
  422.     }
  423.     public function addOrder(Order $order): self
  424.     {
  425.         if (!$this->orders->contains($order)) {
  426.             $this->orders[] = $order;
  427.             $order->setFreelancer($this);
  428.         }
  429.         return $this;
  430.     }
  431.     public function removeOrder(Order $order): self
  432.     {
  433.         if ($this->orders->contains($order)) {
  434.             $this->orders->removeElement($order);
  435.             // set the owning side to null (unless already changed)
  436.             if ($order->getFreelancer() === $this) {
  437.                 $order->setFreelancer(null);
  438.             }
  439.         }
  440.         return $this;
  441.     }
  442.     public function getIsEnabled(): ?bool
  443.     {
  444.         return $this->isEnabled;
  445.     }
  446.     /**
  447.      * @param bool $isEnabled
  448.      */
  449.     public function setIsEnabled(bool $isEnabled): void
  450.     {
  451.         $this->isEnabled $isEnabled;
  452.         $this->updateCustomerStatus();
  453.     }
  454.     /**
  455.      * @return Collection|FreelancerAssistant[]
  456.      */
  457.     public function getFreelancerAssistants(): Collection
  458.     {
  459.         return $this->freelancerAssistants;
  460.     }
  461.     public function addFreelancerAssistant(FreelancerAssistant $freelancerAssistant): self
  462.     {
  463.         if (!$this->freelancerAssistants->contains($freelancerAssistant)) {
  464.             $this->freelancerAssistants[] = $freelancerAssistant;
  465.             $freelancerAssistant->setFreelancer($this);
  466.         }
  467.         return $this;
  468.     }
  469.     public function removeFreelancerAssistant(FreelancerAssistant $freelancerAssistant): self
  470.     {
  471.         if ($this->freelancerAssistants->contains($freelancerAssistant)) {
  472.             $this->freelancerAssistants->removeElement($freelancerAssistant);
  473.             // set the owning side to null (unless already changed)
  474.             if ($freelancerAssistant->getFreelancer() === $this) {
  475.                 $freelancerAssistant->setFreelancer(null);
  476.             }
  477.         }
  478.         return $this;
  479.     }
  480.     public function getSoldOutAt(): ?DateTimeInterface
  481.     {
  482.         return $this->soldOutAt;
  483.     }
  484.     public function setSoldOutAt(?DateTimeInterface $soldOutAt): self
  485.     {
  486.         $this->soldOutAt $soldOutAt;
  487.         return $this;
  488.     }
  489.     public function getNbCreditSms(): ?int
  490.     {
  491.         return $this->nbCreditSms;
  492.     }
  493.     public function setNbCreditSms(?int $nbCreditSms): self
  494.     {
  495.         $this->nbCreditSms $nbCreditSms;
  496.         return $this;
  497.     }
  498.     /**
  499.      * @return Collection|FreelancerComment[]
  500.      */
  501.     public function getComments(): Collection
  502.     {
  503.         return $this->comments;
  504.     }
  505.     public function addComment(FreelancerComment $comment): self
  506.     {
  507.         if (!$this->comments->contains($comment)) {
  508.             $this->comments[] = $comment;
  509.             $comment->setFreelancer($this);
  510.         }
  511.         return $this;
  512.     }
  513.     public function removeComment(FreelancerComment $comment): self
  514.     {
  515.         if ($this->comments->contains($comment)) {
  516.             $this->comments->removeElement($comment);
  517.             // set the owning side to null (unless already changed)
  518.             if ($comment->getFreelancer() === $this) {
  519.                 $comment->setFreelancer(null);
  520.             }
  521.         }
  522.         return $this;
  523.     }
  524.     public function getLastCommentExtract(): ?string
  525.     {
  526.         return !$this->comments->isEmpty() ? substr($this->comments->last()->getComment(), 030) : null;
  527.     }
  528.     public function getLogoPath(): ?string
  529.     {
  530.         return $this->logoPath;
  531.     }
  532.     public function setLogoPath(?string $logoPath): self
  533.     {
  534.         $this->logoPath $logoPath;
  535.         return $this;
  536.     }
  537.     public function getBusinessSoftwareName(): string
  538.     {
  539.         if (is_null($this->getBusinessSoftware())) {
  540.             return 'Aucune';
  541.         }
  542.         return FreelancerBusinessSoftwareEnum::getItemName($this->getBusinessSoftware());
  543.     }
  544.     public function getBusinessSoftware(): ?int
  545.     {
  546.         return $this->businessSoftware;
  547.     }
  548.     public function setBusinessSoftware(?int $businessSoftware): self
  549.     {
  550.         $this->businessSoftware $businessSoftware;
  551.         return $this;
  552.     }
  553.     public function getLogoFile()
  554.     {
  555.         return $this->logoFile;
  556.     }
  557.     public function setLogoFile(File $logoPath null)
  558.     {
  559.         $this->logoFile $logoPath;
  560.         if ($logoPath) {
  561.             $this->updatedAt = new DateTime('now');
  562.         }
  563.     }
  564.     public function hasAllStoresIndexableTemporary(): bool
  565.     {
  566.         //if a deadline is set, stores remain indexable
  567.         return !is_null($this->businessModelDeadline);// && $this->businessModelDeadline->format('Y-m-d H:i:s') == self::BUSINESS_MODEL_FAKE_DEADLINE;
  568.     }
  569.     public function markAsAllStoresIndexableTemporary(): void
  570.     {
  571.         $this->setBusinessModelDeadline(new DateTime(self::BUSINESS_MODEL_FAKE_DEADLINE));
  572.     }
  573.     public function getCreditCosts(): Collection
  574.     {
  575.         return $this->creditCosts;
  576.     }
  577.     public function setCreditCosts(Collection $creditCosts): void
  578.     {
  579.         $this->creditCosts $creditCosts;
  580.     }
  581.     public function addCreditCost(CreditCost $creditCost): self
  582.     {
  583.         if (!$this->creditCosts->contains($creditCost)) {
  584.             $this->creditCosts->add($creditCost);
  585.             $creditCost->setAdministrator($this);
  586.         }
  587.         return $this;
  588.     }
  589.     public function getOnboardingStatus(): string
  590.     {
  591.         return $this->onboardingStatus;
  592.     }
  593.     public function setOnboardingStatus(string $onboardingStatus): void
  594.     {
  595.         $this->onboardingStatus $onboardingStatus;
  596.     }
  597.     public function getNbCreditPurchased(): int
  598.     {
  599.         return $this->nbCreditPurchased;
  600.     }
  601.     public function setNbCreditPurchased(int $nbCreditPurchased): void
  602.     {
  603.         $this->nbCreditPurchased $nbCreditPurchased;
  604.     }
  605.     public function getNbLeadDelivered(): int
  606.     {
  607.         return $this->nbLeadDelivered;
  608.     }
  609.     public function setNbLeadDelivered(int $nbLeadDelivered): void
  610.     {
  611.         $this->nbLeadDelivered $nbLeadDelivered;
  612.     }
  613.     public function getNbSales(): int
  614.     {
  615.         return $this->nbSales;
  616.     }
  617.     public function setNbSales(int $nbSales): void
  618.     {
  619.         $this->nbSales $nbSales;
  620.     }
  621.     /**
  622.      */
  623.     public function hasAtLeastOneIndexableStore(): bool
  624.     {
  625.         return $this->getStores()->exists(function ($key$store) {
  626.             return $store->hasCurrentStoreIndexation();
  627.         });
  628.     }
  629.     public function getAgeCapping(): ?int
  630.     {
  631.         return 50;
  632.     }
  633.     public function isSchedulingAppointment(): bool
  634.     {
  635.         return $this->schedulingAppointment;
  636.     }
  637.     public function setSchedulingAppointment(bool $schedulingAppointment): void
  638.     {
  639.         $this->schedulingAppointment $schedulingAppointment;
  640.     }
  641. }