<?php
namespace App\Entity;
use App\Domain\ESignature\Adapters\Gateway\Doctrine\ESignatureEntity;
use App\Entity\Reference\ReferencePropertyServiceType;
use App\Entity\Reference\VatReference;
use App\Exception\MandateException;
use Exception;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use App\Controller\Manager\Mandate\RefuseMandateController;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use App\Controller\Manager\Mandate\ValidateMandateController;
use App\Entity\Reference\ReferenceTransactionStatus;
use App\Entity\Reference\ReferenceMandateStatus;
use App\Entity\Reference\ReferencePropertyType;
use App\Entity\Reference\ReferenceMandateType;
use App\Repository\MandateRepository;
use App\Traits\TimeStampTrait;
/**
* @ApiResource(
* normalizationContext={"groups"={"mandate:read"}, "swagger_definition_name"="Read"},
* denormalizationContext={"groups"={"mandate:write"}, "swagger_definition_name"="Write"},
*
* itemOperations={
* "get"={
* "security"="is_granted('ROLE_AGENT') or is_granted('ROLE_MANAGER')",
* "security_message" = "Vous n'avez pas le droit de visualiser ce mandat ."
* },
* "put" = {
* "security"="is_granted('PUT', object)",
* "denormalization_context"={"groups"={"mandate:put"}},
* },
* "validate_mandate" = {
* "denormalization_context"={"groups"= {"mandate:validate"} },
* "security"="is_granted('ROLE_MANAGER')",
* "method"="PATCH",
* "path"="/mandate/{id}/validate",
* "controller"=ValidateMandateController::class,
* "read"=true,
* "write"=false,
* },
* "refuse_mandate"={
* "denormalization_context"={"groups"= {"mandate:refuse"} },
* "security"="is_granted('ROLE_MANAGER')",
* "method"="PATCH",
* "path"="/mandate/{id}/refuse",
* "controller"=RefuseMandateController::class,
* "read"=true,
* "write"=true,
* },
* },
*
* collectionOperations={
* "get"={
* "access_control"="is_granted('ROLE_AGENT') or is_granted('ROLE_MANAGER')"
* },
* "post"={
* "access_control" = "is_granted('POST', object)",
* },
* },
* attributes={
* "pagination_items_per_page"=10,
* "formats"={"jsonld", "json", "html", "jsonhal", "csv"={"text/csv"} },
* "order"={"id":"DESC"}
* },
* )
* @ApiFilter(OrderFilter::class, properties={"id"} , arguments= {"orderParameterName" : "order"})
* @ApiFilter(SearchFilter::class, properties={
* "id": "exact",
* "property.collaborator.id": "exact",
* "property.collaborator.firstName": "ipartial",
* "property.collaborator.lastName": "ipartial",
* "property.collaborator.professionalEmail": "partial",
* "referenceMandateType.id":"exact",
* "referenceMandateStatus.id": "exact",
* "referenceMandateStatus.code": "exact",
* "referencePropertyType.id": "exact",
* "property.referenceServiceType.code": "exact",
* "property.referenceServiceType.id": "exact",
* "property.id": "exact",
* "key": "partial",
* "property.contact.id": "exact"
*
* })
* @ORM\Entity(repositoryClass=MandateRepository::class)
* @ORM\HasLifecycleCallbacks()
* @ORM\EntityListeners({"App\EventListener\MandateListener"})
*/
class Mandate implements Loggable
{
use TimeStampTrait;
public const DEFAULT_VALIDATION_PERIOD = 3;
public const SECOND_VALIDATION_PERIOD = 6;
public const THIRD_VALIDATION_PERIOD = 9;
public const FOURTH_VALIDATION_PERIOD = 12;
public const ALL_VALIDATION_PERIOD = [
self::DEFAULT_VALIDATION_PERIOD,
self::SECOND_VALIDATION_PERIOD,
self::THIRD_VALIDATION_PERIOD,
self::FOURTH_VALIDATION_PERIOD
];
public const DEFAULT_AFTER_VALIDATION_PERIOD = 6;
public const SECOND_AFTER_VALIDATION_PERIOD = 9;
public const THIRD_AFTER_VALIDATION_PERIOD = 12;
public const ALL_AFTER_VALIDATION_PERIOD = [
self::DEFAULT_AFTER_VALIDATION_PERIOD,
self::SECOND_AFTER_VALIDATION_PERIOD,
self::THIRD_AFTER_VALIDATION_PERIOD
];
public const REFERENCE_MANDATE_STATUS_CODE_INVALID = [
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_ARCHIVED,
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_REFUSE
];
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*
* @Groups({
* "mandate:read",
* "property:read",
* "transaction:collection:read",
* "business-indication:item:read",
* "business-indication:collection:read",
* "business-indication:item:read",
* "transaction-item:read", "business-indication:item:read",
* "property-visit:read"
* })
*/
private ?int $id;
/**
* @ORM\ManyToOne(targetEntity=Property::class, inversedBy="mandates")
* @Assert\NotBlank
*
* @Groups({
* "mandate:read","mandate:write",
* "transaction-item:read",
* "business-indication:item:read","business-indication:collection:read",
* "property-visit:read",
* "contact:read",
* })
*
*/
private ?Property $property;
/**
* @ORM\Column(type="float",nullable=true)
*
* @Groups({"mandate:read","transaction-item:read", "business-indication:item:read"})
*/
private ?float $maximumPrice;
/**
* @ORM\Column(type="float", nullable=true)
*
* @Groups({"mandate:read","transaction-item:read", "business-indication:item:read"})
*
*/
private ?float $minimalArea;
/**
* @ORM\Column(type="float", nullable=true)
*
* @Groups({"mandate:read","transaction-item:read", "business-indication:item:read"})
*
*/
private ?float $maximalArea;
/**
* @ORM\ManyToOne(targetEntity=ReferenceMandateType::class, inversedBy="mandates")
* @ORM\JoinColumn(nullable=false)
*
* @Assert\NotBlank
* @Groups({"mandate:read","mandate:write","mandate:put",
* "transaction-item:read", "business-indication:item:read"
* })
*/
private ?ReferenceMandateType $referenceMandateType;
/**
* @ORM\ManyToOne(targetEntity=ReferenceMandateStatus::class)
* @ORM\JoinColumn(nullable=false)
*
* @Groups({"mandate:read","transaction-item:read", "business-indication:item:read","property:read"})
*/
private ?ReferenceMandateStatus $referenceMandateStatus;
/**
* @ORM\ManyToOne(targetEntity=ReferencePropertyType::class)
*
* @Groups({"mandate:read"})
*/
private ?ReferencePropertyType $referencePropertyType;
/**
* @ORM\Column(type="string",nullable=true)
*
* @Groups({
* "mandate:read","property-visit:read", "property:read",
* "transaction:collection:read","transaction-item:read", "business-indication:item:read",
* "business-indication:collection:read","business-indication:item:read"
* })
*/
private ?string $key;
/**
* @ORM\Column(type="float", nullable=true)
*
* @Groups({"mandate:read","mandate:write","mandate:put"})
*/
private ?float $commission = 0;
/**
* @ORM\Column(type="boolean", nullable=true)
*
* @Groups({"mandate:read","mandate:write","mandate:put"})
*/
private ?bool $isFreeEntry = null;
/**
* @ORM\ManyToMany(targetEntity=PropertyVisitVoucher::class, inversedBy="mandates")
*/
private $propertyVisitVouchers;
/**
* @ORM\OneToMany(targetEntity=Transaction::class, mappedBy="mandate")
*/
private ?Collection $transactions = null;
/**
* @Groups({
* "mandate:read","mandate:put","mandate:write",
* "transaction:collection:read","transaction-item:read",
* "business-indication:collection:read","business-indication:item:read","business-indication:item:read",
* "property-visit:read",
* })
*
* @ORM\Column(type="float", nullable=true)
*/
private ?float $commissionPercent = null;
/**
* @ORM\Column(type="integer",nullable=true)
*
* @Groups({
* "mandate:read","mandate:put"
* })
*/
private ?int $duration = self::DEFAULT_VALIDATION_PERIOD;
/**
* @ORM\Column(type="integer", nullable=true)
*
* @Groups({
* "mandate:read","mandate:put"
* })
*/
private ?int $exclusiveDuration = self::DEFAULT_AFTER_VALIDATION_PERIOD;
/**
* @ORM\ManyToMany(targetEntity=Property::class)
* @Groups({
* "mandate:read","mandate:put"
* })
* @ApiSubresource()
*/
private ?Collection $additionalProperties = null;
/**
* @ORM\ManyToMany(targetEntity=Contact::class)
* @Groups({
* "mandate:read","mandate:put"
* })
* @ApiSubresource()
*/
private ?Collection $additionalContacts = null;
/**
* @ORM\Column(type="boolean", nullable=true, options={"default"=false})
*
* @Groups({
* "mandate:read"
* })
*/
private ?bool $isAdditionalProperty = false;
/**
* @ORM\Column(type="string", nullable=true)
* @Assert\NotBlank
* @Groups({
* "mandate:read","mandate:put","mandate:write"
* })
*/
private ?string $referenceVat = null;
/**
* @ORM\Column(type="float", nullable=true)
* @Groups({"mandate:read","mandate:put","mandate:write",
* "property:read","property:write","property:put"
* })
*/
private $commissionRecommenderPercent;
/**
* @ORM\Column(type="string", nullable=true)
* @Groups({"mandate:read"})
*/
private ?string $esign ;
public function __construct()
{
$this->propertyVisitVouchers = new ArrayCollection();
$this->transactions = new ArrayCollection();
$this->additionalProperties = new ArrayCollection();
$this->additionalContacts = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* @return string|null
*/
public function getEsign(): ?string
{
return $this->esign;
}
/**
* @param string|null $esign
*/
public function setEsign(?string $esign): void
{
$this->esign = $esign;
}
public function getMaximumPrice(): ?float
{
return $this->maximumPrice;
}
public function setMaximumPrice(?float $maximumPrice): self
{
$this->maximumPrice = $maximumPrice;
return $this;
}
public function getMaximalArea(): ?float
{
return $this->maximalArea;
}
public function setMaximalArea(?float $maximalArea): self
{
$this->maximalArea = $maximalArea;
return $this;
}
public function getMinimalArea(): ?float
{
return $this->minimalArea;
}
public function setMinimalArea(?float $minimalArea): self
{
$this->minimalArea = $minimalArea;
return $this;
}
public function getReferenceMandateType(): ?ReferenceMandateType
{
return $this->referenceMandateType;
}
public function setReferenceMandateType(?ReferenceMandateType $referenceMandateType): self
{
$this->referenceMandateType = $referenceMandateType;
return $this;
}
public function getProperty(): ?Property
{
return $this->property;
}
public function setProperty(?Property $property): self
{
$this->property = $property;
return $this;
}
public function getReferenceMandateStatus(): ?ReferenceMandateStatus
{
return $this->referenceMandateStatus;
}
public function setReferenceMandateStatus(?ReferenceMandateStatus $referenceMandateStatus): self
{
$this->referenceMandateStatus = $referenceMandateStatus;
return $this;
}
public function getReferencePropertyType(): ?ReferencePropertyType
{
return $this->referencePropertyType;
}
public function setReferencePropertyType(?ReferencePropertyType $referencePropertyType): self
{
$this->referencePropertyType = $referencePropertyType;
return $this;
}
public function getKey(): ?string
{
return $this->key;
}
public function setKey(?string $key): self
{
$this->key = $key;
return $this;
}
public function getCommission(): ?float
{
return $this->commission;
}
public function setCommission(?float $commission): self
{
$this->commission = $commission;
return $this;
}
public function getIsFreeEntry(): ?bool
{
return $this->isFreeEntry;
}
public function setIsFreeEntry(?bool $isFreeEntry): self
{
$this->isFreeEntry = $isFreeEntry;
return $this;
}
/**
* @return Collection<int, PropertyVisitVoucher>
*/
public function getPropertyVisitVouchers(): Collection
{
return $this->propertyVisitVouchers;
}
public function addPropertyVisitVoucher(PropertyVisitVoucher $propertyVisitVoucher): self
{
if (!$this->propertyVisitVouchers->contains($propertyVisitVoucher)) {
$this->propertyVisitVouchers[] = $propertyVisitVoucher;
}
return $this;
}
public function removePropertyVisitVoucher(PropertyVisitVoucher $propertyVisitVoucher): self
{
$this->propertyVisitVouchers->removeElement($propertyVisitVoucher);
return $this;
}
/**
* @return Collection|Transaction[]
*/
public function getTransactions(): Collection
{
return $this->transactions;
}
/**
* @param Transaction $transaction
*
* @return $this
*/
public function addTransaction(Transaction $transaction): self
{
if (!$this->transactions->contains($transaction)) {
$this->transactions[] = $transaction;
$transaction->setMandate($this);
}
return $this;
}
/**
* @param Transaction $transaction
*
* @return $this
*/
public function removeTransaction(Transaction $transaction): self
{
// set the owning side to null (unless already changed)
if ($this->transactions->removeElement($transaction) && $transaction->getMandate() === $this) {
$transaction->setMandate(null);
}
return $this;
}
/**
* check if agent has an already generated property visit
*
* @param int $agentId
*
* @return bool
*/
public function agentHasAlreadyPropertyVisitGenerated(int $agentId): bool
{
foreach ($this->getPropertyVisitVouchers() as $visitVoucher) {
if ($visitVoucher->getCreatedBy() === $agentId) {
return true;
}
}
return false;
}
/**
* Check if mandate had already a valid transaction
*
* @param int $agentId
*
* @return bool
*/
public function hasValidTransaction(int $agentId): bool
{
return $this->transactions->filter(function (Transaction $transaction) use ($agentId) {
return
$transaction->getOutputAgent()->getId() === $agentId &&
in_array(
$transaction->getReferenceTransactionStatus()->getCode(),
ReferenceTransactionStatus::ALL_TRANSACTION_VALID_CODE_STATUS,
true
);
})->count() > 0;
}
/**
* Check if agent can generate transaction for the current mandate
*
* @return bool
*/
public function canGenerateTransaction(): bool
{
return $this->transactions->filter(function (Transaction $transaction) {
return
in_array(
$transaction->getReferenceTransactionStatus()->getCode(),
ReferenceTransactionStatus::ALL_TRANSACTION_RESERVED_CODE_STATUS,
true
);
})->count() <= 0 &&
in_array($this->referenceMandateStatus->getCode(), [
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_VALID,
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_RESERVED,
], true) &&
!$this->isBusinessIndicationMandate();
}
/**
* Check if the agent can generate visit voucher for the current mandate
*
* @return bool
*/
public function canGenerateVisitVoucher(): bool
{
return ($this->transactions->filter(function (Transaction $transaction) {
return
in_array(
$transaction->getReferenceTransactionStatus()->getCode(),
ReferenceTransactionStatus::ALL_TRANSACTION_RESERVED_CODE_STATUS,
true
);
})->count() <= 0) &&
($this->isValid() || $this->isReserved()) &&
(!in_array($this->property->getReferenceServiceType()->getCode(), [
ReferencePropertyServiceType::REFERENCE_CODE_PROPERTY_SERVICE_TYPE_SEARCH_BUY,
ReferencePropertyServiceType::REFERENCE_CODE_PROPERTY_SERVICE_TYPE_SEARCH_RENT
]));
}
/**
* check if the mandate has valid status
*
* @return bool
*/
public function isValid(): bool
{
return $this->getReferenceMandateStatus()->getCode() ===
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_VALID;
}
/**
* check if the mandate has reserved status
*
* @return bool
*/
public function isReserved(): bool
{
return $this->getReferenceMandateStatus()->getCode() ===
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_RESERVED;
}
public function getCommissionPercent(): ?float
{
return $this->commissionPercent;
}
public function setCommissionPercent(?float $commissionPercent): self
{
$this->commissionPercent = $commissionPercent;
return $this;
}
public function getDuration(): ?int
{
return $this->duration;
}
public function setDuration(int $duration): self
{
if (!in_array($duration, self::ALL_VALIDATION_PERIOD, true)) {
throw new \InvalidArgumentException("Invalid period for mandate");
}
$this->duration = $duration;
return $this;
}
public function getExclusiveDuration(): ?int
{
return $this->exclusiveDuration;
}
public function setExclusiveDuration(int $exclusiveDuration): self
{
if (!in_array($exclusiveDuration, self::ALL_AFTER_VALIDATION_PERIOD, true)) {
throw new \InvalidArgumentException("Invalid exclusive duration for mandate");
}
$this->exclusiveDuration = $exclusiveDuration;
return $this;
}
/**
* @return Collection<int, Property>
*/
public function getAdditionalProperties(): Collection
{
return $this->additionalProperties;
}
public function addAdditionalProperty(Property $additionalProperty): self
{
if (!$this->additionalProperties->contains($additionalProperty)) {
$this->additionalProperties[] = $additionalProperty;
}
return $this;
}
public function removeAdditionalProperty(Property $additionalProperty): self
{
$this->additionalProperties->removeElement($additionalProperty);
return $this;
}
public function getAdditionalContacts(): Collection
{
return $this->additionalContacts;
}
public function addAdditionalContact(Contact $additionalContact): self
{
if (!$this->additionalContacts->contains($additionalContact)) {
$this->additionalContacts[] = $additionalContact;
}
return $this;
}
public function removeAdditionalContact(Contact $additionalContact): self
{
$this->additionalContacts->removeElement($additionalContact);
return $this;
}
/**
* @return bool|null
*/
public function getIsAdditionalProperty(): ?bool
{
return $this->isAdditionalProperty;
}
/**
* @param bool $isAdditionalProperty
*
* @return $this
*/
public function setIsAdditionalProperty(bool $isAdditionalProperty): self
{
$this->isAdditionalProperty = $isAdditionalProperty;
return $this;
}
public function isInvalid(): bool
{
return in_array(
$this->getReferenceMandateStatus()->getCode(),
self::REFERENCE_MANDATE_STATUS_CODE_INVALID,
true
);
}
/**
* check if the mandate is inProgress
*
* @return bool
*/
public function isInProgress(): bool
{
if (null === $status = $this->getReferenceMandateStatus()) {
return false;
}
return $status->isInProgress();
}
/**
* check if we can display the action "% share" or not
*
* @return bool
*/
public function canShareCommission(): bool
{
return $this->getReferenceMandateStatus()->getCode() ===
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_IN_PROGRESS;
}
/**
* @return bool
*/
public function isBusinessIndicationMandate(): bool
{
$contact = $this->property->getContact();
return $contact->getIsBusinessIndication()
&& $contact->isCompany()
&& $this->property->isNew()
&& $this->property->isSale();
}
public function getReferenceVat(): ?string
{
return $this->referenceVat;
}
/**
* @param string $referenceVat
* @return $this
*/
public function setReferenceVat(string $referenceVat): self
{
$this->referenceVat = $referenceVat;
return $this;
}
/**
* @throws Exception
*/
public function calculateCommission(): void
{
$property = $this->getProperty();
if ($this->getIsFreeEntry()) {
return;
}
$commissionPercent = $this->getCommissionPercent();
$price = $property->getPrice();
if ($this->getReferenceVat() === VatReference::REFERENCE_CODE_VAT_TAXINCLUDED) {
$price *= VatReference::VAT_RATE;
}
if ($property->isRent() && $commissionPercent > 100) {
throw new Exception("Merci de saisir un pourcentage inférieur à 100");
}
$commission = $price * ($commissionPercent / 100);
$this->setCommission($commission);
}
/**
* @return array
*/
public function getFields(): array
{
return [
'referenceMandateStatus' => 'referenceMandateStatus',
'commission' => 'commission',
'commissionPercent' => 'commissionPercent',
'isFreeEntry' => 'isFreeEntry',
'additionalProperties' => 'additionalProperties'
];
}
public static function isOwner(Mandate $mandate, AbstractCollaborator $currentUser): bool
{
$property = $mandate->getProperty();
$collaborator = $property->getCollaborator();
return $collaborator->getId() === $currentUser->getId();
}
public function getCommissionRecommenderPercent(): ?float
{
return $this->commissionRecommenderPercent ?? 12;
}
public function setCommissionRecommenderPercent(?float $commissionRecommenderPercent): self
{
$this->commissionRecommenderPercent = $commissionRecommenderPercent;
return $this;
}
/**
* check if we can display the action "% share" or not
*
* @return bool
*/
public function canUpdateMandate(): bool
{
return $this->getReferenceMandateStatus()->getCode() ===
ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_IN_PROGRESS;
}
/**
*
* @return bool
*/
public function canShowESignature(): bool
{
return $this->getEsign() == null && $this->getReferenceMandateStatus()->getCode() === ReferenceMandateStatus::REFERENCE_CODE_MANDATE_STATUS_IN_PROGRESS;
}
}