<?php
namespace App\Entity;
use App\Entity\Enum\BusinessModelEnum;
use App\Entity\Enum\ProspectOnStoreCreationModeEnum;
use App\Entity\Enum\ProspectOnStoreStatusEnum;
use App\Entity\Prescriber\Prescriber;
use App\Entity\Prescriber\PrescriberRemuneration;
use DateTime;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity(repositoryClass="App\Repository\ProspectOnStoreRepository")
* @ORM\EntityListeners({"App\Listener\ProspectOnStoreListener"})
* @ApiResource(
* itemOperations={
* "get"={"security"="is_granted('ROLE_API')"},
* "patch"={"security"="is_granted('ROLE_API') and object.isWaitingQualification()"},
* "qualify"={
* "security"="is_granted('ROLE_API')",
* "method"="POST",
* "path"="/prospect/{id}/qualify",
* "controller"=App\Controller\Api\QualifyProspectAction::class,
* },
* "getFromHubspot"={
* "security"="is_granted('ROLE_API')",
* "method"="GET",
* "path"="/hubspot/prospect_on_stores/{hubspotId}",
* "controller"=App\Controller\Api\getPosFromHubspotAction::class,
* "read"=false
* }
* },
* normalizationContext={"groups"={"pos:read"}},
* denormalizationContext={"groups"={"pos:write", "prospect:write"}}
* )
*/
class ProspectOnStore
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
* @Groups({"pos:read"})
*/
private $id;
/**
* @ORM\Column(type="datetime")
* @Gedmo\Timestampable(on="create")
*/
private $created;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Store", inversedBy="prospectsOnStore", fetch="EAGER")
* @ORM\JoinColumn(nullable=false)
* @Groups({"pos:write", "pos:read"})
*/
private $store;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Prospect", inversedBy="prospectsOnStore")
* @ORM\JoinColumn(nullable=false)
* @Groups({"pos:write", "pos:read"})
*/
private $prospect;
/**
* @deprecated
* @ORM\Column(type="integer", nullable=true)
*/
private $creditCost;
/**
* @ORM\OneToOne(targetEntity=CreditCost::class, inversedBy="prospectOnStore", cascade={"persist", "remove"})
*/
private ?CreditCost $cost = null;
/**
* @ORM\OneToMany(targetEntity="App\Entity\MediaoptinDelivery", mappedBy="prospectOnStore", cascade={"persist", "remove"})
*/
private $mediaoptinDeliveries;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $isDemandForThird;
/**
* A freelancer with role "BUSINESS_INTRODUCER" can mark prospect as "delivered" on his BO
* @ORM\Column(type="boolean", nullable=true)
*/
private $markDeliveredByBusinessIntroducer;
/**
* @ORM\Column(type="boolean")
* @var boolean
*/
private $isUnreachableSmsSend = false;
/**
* @ORM\Column(type="boolean", nullable=true)
*/
private $waitingQualification;
/**
* Note de l'admin (freelancer) sur le prospect
*
* @ORM\OneToMany(targetEntity="App\Entity\ProspectOnStoreStatus", mappedBy="prospectOnStore", cascade={"persist", "remove"})
* @ORM\JoinColumn(nullable=true)
* @ORM\OrderBy({"created" = "DESC"})
*/
private $prospectOnStoreStatus;
/**
* ProspectDuplicate that has created this prospectOnStore (optional)
* @ORM\ManyToOne(targetEntity=ProspectDuplicate::class, inversedBy="prospectOnStoresCreated")
*/
private $prospectDuplicateOrigin;
/**
* @ORM\ManyToOne(targetEntity=ProspectOnStoreFeedback::class, inversedBy="prospectOnStoreGenerated")
*/
private $prospectOnStoreFeedbackOrigin;
/**
* @ORM\OneToOne(targetEntity=ProspectOnStoreFeedback::class, inversedBy="prospectOnStore")
* @ORM\JoinColumn(nullable=true)
*/
private $feedback;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $startDealingWithDate;
/**
* Mode d'acquisition du lead (emailing, facebook, ...)
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $acquisitionMode;
/**
* Base emailing de provenance du lead
*
* @ORM\Column(type="string", length=255, nullable=true)
* @Assert\NotBlank(groups={"API"})
* @Groups({"prospect:write"})
*/
private $acquisitionBase;
/**
* Url de provenance du lead (dernière URL avant persist)
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $acquisitionUrl;
/**
* Meta keyword URL
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $acquisitionKeyword;
/**
* @ORM\Column(type="string", length=5, nullable=true)
*/
private $creationMode=ProspectOnStoreCreationModeEnum::STANDARD;
/**
* @ORM\Column(type="boolean")
*/
private $delivered=false;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $distanceToStore;
/**
* @ORM\Column(type="boolean")
*/
private $isSpecific=false;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $comment;
/**
* @ORM\OneToMany(targetEntity=DeviceSale::class, mappedBy="prospectOnStore", orphanRemoval=true)
*/
private $deviceSales;
/**
* @ORM\Column(type="smallint")
*/
private ?int $businessModel;
/**
* @ORM\OneToOne(targetEntity=Claim::class, inversedBy="prospectOnStore")
*/
private ?Claim $claim;
/**
* @ORM\OneToOne(targetEntity=ProspectOnStoreQualification::class, cascade={"persist", "remove"}, inversedBy="prospectOnStore")
*/
private ?ProspectOnStoreQualification $qualification=null;
/**
* @ORM\ManyToOne(targetEntity=Prescriber::class, inversedBy="prospectsOnStore")
*/
private ?Prescriber $prescriber=null;
/**
* @ORM\ManyToOne(targetEntity=PrescriberRemuneration::class, inversedBy="prospectsOnStore", cascade={"persist"})
*/
private ?PrescriberRemuneration $prescriberRemuneration=null;
public function __construct()
{
$this->mediaoptinDeliveries = new ArrayCollection();
$this->prospectOnStoreStatus = new ArrayCollection();
$this->deviceSales = new ArrayCollection();
}
/**
* Représente l'entité dans le BO
* @return string
*/
public function getLabel(){
return '#'.$this->getId();
}
public function getId(): ?int
{
return $this->id;
}
public function getStore(): ?Store
{
return $this->store;
}
public function setStore(?Store $store): self
{
$this->store = $store;
$businessModel = $store->getAdministrator()->getBusinessModel();
if(is_null($businessModel)){
$businessModel = BusinessModelEnum::CREDIT;
}
$this->businessModel = $businessModel;
return $this;
}
public function getProspect(): ?Prospect
{
return $this->prospect;
}
public function setProspect(?Prospect $prospect): self
{
$this->prospect = $prospect;
return $this;
}
/**
* @deprecated
* @return int|null
*/
public function getCreditCost(): ?int
{
return $this->creditCost;
}
/**
* @deprecated
* @param int $creditCost
* @return $this
*/
public function setCreditCost(int $creditCost): self
{
$this->creditCost = $creditCost;
return $this;
}
public function getCost(): ?CreditCost
{
return $this->cost;
}
public function setCost(?CreditCost $cost): self
{
$this->cost = $cost;
$cost->setProspectOnStore($this);
return $this;
}
/**
* @return Collection|MediaoptinDelivery[]
*/
public function getMediaoptinDeliveries(): Collection
{
return $this->mediaoptinDeliveries;
}
public function addMediaoptinDelivery(MediaoptinDelivery $mediaoptinDelivery): self
{
if (!$this->mediaoptinDeliveries->contains($mediaoptinDelivery)) {
$this->mediaoptinDeliveries[] = $mediaoptinDelivery;
$mediaoptinDelivery->setProspectOnStore($this);
}
return $this;
}
public function removeMediaoptinDelivery(MediaoptinDelivery $mediaoptinDelivery): self
{
if ($this->mediaoptinDeliveries->contains($mediaoptinDelivery)) {
$this->mediaoptinDeliveries->removeElement($mediaoptinDelivery);
// set the owning side to null (unless already changed)
if ($mediaoptinDelivery->getProspectOnStore() === $this) {
$mediaoptinDelivery->setProspectOnStore(null);
}
}
return $this;
}
public function getIsDemandForThird(): ?bool
{
return $this->isDemandForThird;
}
public function setIsDemandForThird(?bool $isDemandForThird): self
{
$this->isDemandForThird = $isDemandForThird;
return $this;
}
/**
* @return mixed
*/
public function isMarkDeliveredByBusinessIntroducer()
{
return $this->markDeliveredByBusinessIntroducer;
}
/**
* @param mixed $markDeliveredByBusinessIntroducer
*/
public function setMarkDeliveredByBusinessIntroducer($markDeliveredByBusinessIntroducer): void
{
$this->markDeliveredByBusinessIntroducer = $markDeliveredByBusinessIntroducer;
}
public function getMarkDeliveredByBusinessIntroducer(): ?bool
{
return $this->markDeliveredByBusinessIntroducer;
}
public function getIsUnreachableSmsSend(): ?bool
{
return $this->isUnreachableSmsSend;
}
public function setIsUnreachableSmsSend(bool $isUnreachableSmsSend): self
{
$this->isUnreachableSmsSend = $isUnreachableSmsSend;
return $this;
}
public function getCreated(): ?DateTimeInterface
{
return $this->created;
}
public function setCreated(DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
/**
* Return the latest date among the creation date of the POS, OR the validation of the qualification
* @return DateTimeInterface|null
*/
public function getCreationOrQualificationValidationDate(): ?DateTimeInterface
{
$qualification = $this->getProspect()->getQualification();
if($qualification && $qualification->isValidated()){
return max($qualification->getValidatedAt(), $this->created) ;
}
return $this->created;
}
/**
* @return bool
*/
public function isWaitingQualification()
{
return $this->waitingQualification;
}
/**
* @param bool $waitingQualification
*/
public function setWaitingQualification(bool $waitingQualification): void
{
$this->waitingQualification = $waitingQualification;
}
/**
* @return Collection|ProspectOnStoreStatus[]
*/
public function getProspectOnStoreStatus(): Collection
{
return $this->prospectOnStoreStatus;
}
/**
* Verify number of contact attempt status
* @return bool
*/
public function hasBeenContactedManyTimes(): bool
{
$nbContact = 0;
foreach ($this->getProspectOnStoreStatus() as $status){
if($status->getStatus() == ProspectOnStoreStatusEnum::TO_CONTACT){
$nbContact++;
if($nbContact >= 3){
return true;
}
}
}
return false;
}
/**
* Count number of contact attempt by freelancer
* @return int
*/
public function countContactAttempt(): int
{
$nbContact = 0;
foreach ($this->getProspectOnStoreStatus() as $status){
if($status->getStatus() == ProspectOnStoreStatusEnum::TO_CONTACT){
$nbContact++;
}
}
return $nbContact;
}
/**
* Verify delay between two contact attempts (for blocked buttons in front)
* @return bool
*/
public function isContactAttemptAllowed(): bool
{
$now = new DateTime();
foreach ($this->getProspectOnStoreStatus() as $status){
if($status->getStatus() == ProspectOnStoreStatusEnum::TO_CONTACT){
$diff = $status->getCreated()->diff($now);
$diffInMinutes = $diff->days * 24 * 60 + $diff->h * 60 + $diff->i;
if ($diffInMinutes < 15) {
return false;
}
}
}
return true;
}
/**
* Return the delay (expressed in nb of hours) between the creation (or qualification) of the lead and the creation of the first status.
* Null if no status
* @return int|null
*/
public function getDelayBeforeFirstProspectOnStoreStatus() : ?int
{
if($firstStatus = $this->getProspectOnStoreStatus()->last()){
$diff = $this->getCreationOrQualificationValidationDate()->diff($firstStatus->getCreated());
return $diff->format('%h');
}
return null;
}
public function getLastStatus(){
return $this->getProspectOnStoreStatus()->first();
}
/**
* Does the status is "RDV PRIS" or later
* false if no status
* @return bool
*/
public function hasStatusAppointmentMadeOrLater() : bool
{
$lastStatus = $this->getLastStatus();
if($lastStatus){
return in_array($lastStatus->getStatus(), ProspectOnStoreStatusEnum::getAvailableTypesAfterAppointment());
}
return false;
}
public function removeProspectOnStoreStatus(ProspectOnStoreStatus $prospectOnStoreStatus): self
{
if ($this->prospectOnStoreStatus->contains($prospectOnStoreStatus)) {
$this->prospectOnStoreStatus->removeElement($prospectOnStoreStatus);
// set the owning side to null (unless already changed)
if ($prospectOnStoreStatus->getProspect() === $this) {
$prospectOnStoreStatus->setProspect(null);
}
}
return $this;
}
public function getProspectDuplicateOrigin(): ?ProspectDuplicate
{
return $this->prospectDuplicateOrigin;
}
public function setProspectDuplicateOrigin(?ProspectDuplicate $prospectDuplicateOrigin): self
{
$this->prospectDuplicateOrigin = $prospectDuplicateOrigin;
$this->setCreationMode(ProspectOnStoreCreationModeEnum::DUPLICATE);
return $this;
}
/**
* Specify if this POS has been created from a feedback (user not contacted)
* @return bool
*/
public function hasFeedbackOrigin(): bool
{
return !is_null($this->prospectOnStoreFeedbackOrigin);
}
public function getProspectOnStoreFeedbackOrigin(): ?ProspectOnStoreFeedback
{
return $this->prospectOnStoreFeedbackOrigin;
}
public function setProspectOnStoreFeedbackOrigin(?ProspectOnStoreFeedback $prospectOnStoreFeedbackOrigin): self
{
$this->prospectOnStoreFeedbackOrigin = $prospectOnStoreFeedbackOrigin;
$this->setCreationMode(ProspectOnStoreCreationModeEnum::FEEDBACK);
return $this;
}
/**
* @return mixed
*/
public function getFeedback()
{
return $this->feedback;
}
/**
* @param mixed $feedback
*/
public function setFeedback($feedback): void
{
$this->feedback = $feedback;
}
/**
* @return mixed
*/
public function getStartDealingWithDate()
{
return $this->startDealingWithDate;
}
/**
* @param mixed $startDealingWithDate
*/
public function setStartDealingWithDate($startDealingWithDate): void
{
$this->startDealingWithDate = $startDealingWithDate;
}
/**
* set "startDealingDate" as now (if null before)
* @return true if date as been updated, else false
*/
public function setStartDealingWithDateAsNow(): bool
{
if(is_null($this->startDealingWithDate)){
$this->setStartDealingWithDate(new DateTime());
return true;
}
return false;
}
/**
* Compute startDealingWithDate based on ProspectOnStoreStatus. Only for ComputePosStartDealingDateCommand.
* Can be deleted once command executed once.
* @return bool
* @deprecated
*/
public function computeStartDealingWithDate()
{
if(is_null($this->startDealingWithDate)){
if(!$this->getProspectOnStoreStatus()->isEmpty()){
$firstStatus = $this->getProspectOnStoreStatus()->last();
$this->startDealingWithDate = $firstStatus->getCreated();
return true;
}
}
return false;
}
public function getWaitingQualification(): ?bool
{
return $this->waitingQualification;
}
public function getAcquisitionMode(): ?string
{
return $this->acquisitionMode;
}
public function setAcquisitionMode(?string $acquisitionMode): self
{
$this->acquisitionMode = $acquisitionMode;
return $this;
}
public function getAcquisitionBase(): ?string
{
return $this->acquisitionBase;
}
public function setAcquisitionBase(?string $acquisitionBase): self
{
$this->acquisitionBase = $acquisitionBase;
return $this;
}
public function getAcquisitionUrl(): ?string
{
return $this->acquisitionUrl;
}
public function setAcquisitionUrl(?string $acquisitionUrl): self
{
$this->acquisitionUrl = $acquisitionUrl;
return $this;
}
public function getAcquisitionKeyword(): ?string
{
return $this->acquisitionKeyword;
}
public function setAcquisitionKeyword(?string $acquisitionKeyword): self
{
$this->acquisitionKeyword = $acquisitionKeyword;
return $this;
}
public function getCreationMode(): ?string
{
return $this->creationMode;
}
public function getCreationModeLabel(): ?string
{
return ProspectOnStoreCreationModeEnum::getStateName($this->creationMode);
}
public function setCreationMode(?string $creationMode): self
{
$this->creationMode = $creationMode;
return $this;
}
public function isCreatedFromUnreachable(): bool
{
return $this->creationMode == ProspectOnStoreCreationModeEnum::UNREACH;
}
/**
* Copy utm from a prospect
* @param Prospect $prospect
*/
public function copyUtmFromProspect(Prospect $prospect){
$this->setAcquisitionUrl($prospect->getAcquisitionUrl());
$this->setAcquisitionMode($prospect->getAcquisitionMode());
$this->setAcquisitionBase($prospect->getAcquisitionBase());
$this->setAcquisitionKeyword($prospect->getAcquisitionKeyword());
}
/**
* @deprecated use isDelivered instead
* @return bool|null
*/
public function getDelivered(): ?bool
{
return $this->delivered;
}
public function isDelivered(): ?bool
{
return $this->delivered;
}
public function setDelivered(bool $delivered): self
{
$this->delivered = $delivered;
return $this;
}
public function markDelivered(): self
{
return $this->setDelivered(true);
}
public function getDistanceToStore(): ?float
{
return $this->distanceToStore;
}
public function setDistanceToStore(?float $distanceToStore): self
{
$this->distanceToStore = $distanceToStore;
return $this;
}
public function getIsSpecific(): ?bool
{
return $this->isSpecific;
}
public function setIsSpecific(bool $isSpecific): self
{
$this->isSpecific = $isSpecific;
return $this;
}
public function markSpecificWithComment(string $comment){
$this->setIsSpecific(true);
$this->appendComment($comment);
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): self
{
$this->comment = $comment;
return $this;
}
public function appendComment(?string $comment): self
{
if(is_null($this->comment)){
$this->setComment($comment);
}else{
$this->setComment($this->comment.' | '.$comment);
}
return $this;
}
/**
* @return Collection|DeviceSale[]
*/
public function getDeviceSales(): Collection
{
return $this->deviceSales;
}
/**
* @return bool
*/
public function hasDeviceSales(): bool
{
return !$this->deviceSales->isEmpty();
}
public function addDeviceSale(DeviceSale $deviceSale): self
{
if (!$this->deviceSales->contains($deviceSale)) {
$this->deviceSales[] = $deviceSale;
$deviceSale->setProspectOnStore($this);
}
return $this;
}
public function removeDeviceSale(DeviceSale $deviceSale): self
{
if ($this->deviceSales->contains($deviceSale)) {
$this->deviceSales->removeElement($deviceSale);
// set the owning side to null (unless already changed)
if ($deviceSale->getProspectOnStore() === $this) {
$deviceSale->setProspectOnStore(null);
}
}
return $this;
}
/**
* @return int|null
*/
public function getBusinessModel(): ?int
{
return $this->businessModel;
}
public function getBusinessModelLabel(): string
{
return BusinessModelEnum::getModelName($this->businessModel);
}
/**
* @param int|null $businessModel
*/
public function setBusinessModel(?int $businessModel): void
{
$this->businessModel = $businessModel;
}
public function isBusinessModelPerformance(): bool
{
return $this->businessModel == BusinessModelEnum::PERFORMANCE;
}
public function isBusinessModelCredit(): bool
{
return $this->businessModel == BusinessModelEnum::CREDIT;
}
public function isBusinessModelPayAsYouGo(): bool
{
return $this->businessModel == BusinessModelEnum::PAY_AS_YOU_GO;
}
/**
* @return Claim|null
*/
public function getClaim(): ?Claim
{
return $this->claim;
}
/**
* @return bool
*/
public function hasClaim(): bool
{
return !is_null($this->claim);
}
/**
* @param Claim|null $claim
*/
public function setClaim(?Claim $claim): void
{
$this->claim = $claim;
}
/**
* @Groups({"pos:read"})
* @return ProspectOnStoreQualification|null
*/
public function getQualification(): ?ProspectOnStoreQualification
{
return $this->qualification;
}
public function setQualification(?ProspectOnStoreQualification $qualification): self
{
$this->qualification = $qualification;
if(is_null($this->qualification->getProspectOnStore())){
$this->qualification->setProspectOnStore($this);
}
return $this;
}
public function getPrescriber(): ?Prescriber
{
return $this->prescriber;
}
public function setPrescriber(?Prescriber $prescriber): self
{
$this->prescriber = $prescriber;
return $this;
}
public function getPrescriberRemuneration(): ?PrescriberRemuneration
{
return $this->prescriberRemuneration;
}
public function setPrescriberRemuneration(?PrescriberRemuneration $prescriberRemuneration): self
{
$this->prescriberRemuneration = $prescriberRemuneration;
return $this;
}
}