<?php
namespace Evo\Infrastructure\MappingORM;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Serializer\Filter\PropertyFilter;
use App\Enum\OrganizationStatusPayedEnum;
use App\Enum\ProductKeyEnum;
use App\Service\SubscriptionUtils;
use App\Validator\Constraints\ServiceConstraint;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @ApiResource(
* mercure="true",
* attributes={
* "normalization_context"={"groups"={"read_subscription"}},
* "denormalization_context"={"groups"={"write_subscription"}},
* },
* subresourceOperations={
* "api_organizations_subscriptions_get_subresource"={
* "method"="GET",
* "normalizationContext"={"groups"={"read_subscription"}},
* },
* },
* itemOperations={
* "get",
* "put",
* "delete"={"security"="is_granted('ROLE_ADMIN')"}
* })
* @ORM\Entity
* @ORM\Table(name="subscription")
* @ORM\Entity(repositoryClass="App\Repository\SubscriptionRepository")
* @ApiFilter(SearchFilter::class, properties={"organization": "exact", "hasBlockedService": "exact"})
* @ApiFilter(PropertyFilter::class)
*
* @ServiceConstraint
*/
class Subscription
{
/**
* @var int The entity Id
*
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue()
* @Groups({"admin_orga_list", "read_subscription", "read_organization", "read_pack"})
*/
private ?int $id = null;
/**
* @var bool The subscription active/inactive
*
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="boolean")
*/
private $active;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="float", nullable=true)
*/
private ?float $price = null;
/**
* @var string The subscription type
*
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="string", nullable=true)
*/
private ?string $type = null;
/**
* @var float The subscription deposit
*
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization", "read_pack"})
* @ORM\Column(type="decimal", precision=13, scale=2, nullable=true)
*/
private $subscriptionDeposit = 0;
/**
* @var \DateTime
*
* @Groups({"read_subscription", "read_organization"})
* @ORM\Column(name="created_at", type="datetime")
*/
private $createdAt;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization", "read_pack"})
* @ORM\Column(type="float", nullable=true)
*/
private $discountWithoutTax;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="array", nullable=true)
*/
private $paymentDates = [];
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization", "read_pack"})
* @ORM\Column(type="float", nullable=true)
*/
private ?float $priceWithoutTax = null;
/**
* @ORM\Column(type="float", nullable=true)
*/
private ?float $storedTotalPriceWithoutTax = null; // used to store the totalPriceWithoutTax to get the first price in changeset on upsell et downsell
/**
* @Groups({"read_subscription", "write_subscription"})
* @ORM\Column(type="boolean", nullable=true)
*/
private $isUpdatedPrice;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization"})
* @ORM\Column(type="datetime", nullable=true)
*/
private ?\DateTimeInterface $nextPayment = null;
/**
* @ORM\Column(type="array", nullable=true)
*/
private $packHistory = [];
/**
* @Groups({"read_subscription", "write_subscription", "read_organization"})
* @ORM\Column(type="boolean", nullable=true)
*/
private $notAcceptedScanEnveloppe;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization"})
* @ORM\Column(type="date", nullable=true)
*/
private ?\DateTimeInterface $invoiceGenerationDate = null;
/**
* @Groups({"read_subscription", "write_subscription"})
* @ORM\ManyToOne(targetEntity=Organization::class, inversedBy="subscriptions")
*/
private ?Organization $organization = null;
/**
* @Groups({"read_subscription", "write_subscription", "read_pack"})
* @ORM\Column(type="integer", nullable=true)
*/
private ?int $monthNumber = null;
/**
* @Groups({"read_subscription", "write_subscription", "read_pack"})
* @ORM\Column(type="array", nullable=true)
*/
private $authorizedReccurency = [];
/**
* @Groups({"read_subscription", "write_subscription", "read_organization"})
* @ORM\Column(type="boolean", nullable=true)
*/
private $isCancelUsingOffer;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization" })
* @ORM\Column(type="boolean", nullable=true)
*/
private $isDigipackOffer = 0;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="integer", nullable=true)
*/
private ?int $deadlineOffered = null;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="integer", options={"default" : 1})
*/
private ?int $prepaidMonths = 1;
/**
* @Groups({"read_subscription", "read_organization", "write_subscription", "write_organization"})
* @ORM\OneToMany(targetEntity=Service::class, mappedBy="subscription", cascade={"persist", "remove"}, orphanRemoval=true)
* @ApiSubresource(maxDepth=1)
*/
private Collection $services;
/**
* @Groups({"read_subscription", "write_subscription", "read_pack"})
* @ORM\OneToMany(targetEntity=Item::class, mappedBy="subscriptionIncluded", cascade={"persist", "remove"})
*/
private Collection $includedProducts;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="boolean", options={"default" : false})
*/
private bool $hasBlockedService = false;
/**
* @ORM\OneToOne(targetEntity=Pack::class, mappedBy="subscription", cascade={"persist"})
*/
private ?Pack $pack = null;
/**
* @Groups({"read_subscription", "write_subscription", "read_organization", "write_organization"})
* @ORM\Column(type="datetime", nullable=true)
*/
private ?\DateTimeInterface $documentRecoveryDelay = null;
/**
* @ORM\Column(type="datetime", nullable=true)
* @Groups({"read_subscription", "write_subscription"})
*/
private ?\DateTimeInterface $updatedAt = null;
/**
* @ORM\OneToMany(targetEntity=CommercialGesture::class, mappedBy="subscription", cascade={"remove"})
*/
private Collection $commercialGestures;
public function __construct()
{
$this->active = true;
$this->createdAt = new \DateTime();
$this->includedProducts = new ArrayCollection();
$this->services = new ArrayCollection();
$this->commercialGestures = new ArrayCollection();
}
public function resetId()
{
$this->id = null;
return $this;
}
/**
* @return bool
*/
public function getActive()
{
return $this->active;
}
public function setActive(bool $active): Subscription
{
$this->active = $active;
return $this;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(float $price): self
{
$this->price = $price;
return $this;
}
public function getSubscriptionDeposit(): ?float
{
return $this->subscriptionDeposit;
}
public function setSubscriptionDeposit(?float $subscriptionDeposit): Subscription
{
$this->subscriptionDeposit = (float) $subscriptionDeposit;
return $this;
}
/**
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* @param \DateTime|\DateTimeImmutable|null $createdAt
*/
public function setCreatedAt(?\DateTimeInterface $createdAt): Subscription
{
$this->createdAt = $createdAt;
return $this;
}
public function getDiscountWithoutTax(): ?string
{
return $this->discountWithoutTax;
}
public function setDiscountWithoutTax(?string $discountWithoutTax): self
{
$this->discountWithoutTax = $discountWithoutTax;
return $this;
}
public function __toString()
{
return '#'.$this->getId();
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @deprecated don't use directly, use SubscriptionUtils->getPackUniqueKey()
* Is override, don't forget declare the groups in SubscriptionNormalizer
*/
public function getPack(): ?string
{
return '';
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
public function setType(?string $type): Subscription
{
$this->type = $type;
return $this;
}
/**
* @deprecated but don't remove it, it's used in the migration
*/
public function getProducts(): array
{
return $this->getProductsFromServices();
}
/*
* Is the new getProducts() method
*/
public function getProductsFromServices(): array
{
$products = [];
foreach ($this->getServices() as $service) {
$products[] = $service->getProduct();
}
return $products;
}
public function getPreviousPack()
{
if (!empty($this->getPackHistory())) {
$histories = $this->getPackHistory();
if (isset($histories[count($histories) - 2])) {
return $histories[count($histories) - 2];
}
}
return null;
}
public function getPackHistory(): ?array
{
return $this->packHistory;
}
public function setPackHistory(?array $packHistory): self
{
$this->packHistory = $packHistory;
// si le tableau a plus de 4 éléments, on supprime le premier
if (\count($this->packHistory) > 4) {
array_shift($this->packHistory);
}
return $this;
}
public function getPaymentDates(): ?array
{
return $this->paymentDates;
}
public function setPaymentDates(?array $paymentDates): self
{
$this->paymentDates = $paymentDates;
return $this;
}
public function getPriceWithoutTax(): ?float
{
return $this->priceWithoutTax;
}
public function setPriceWithoutTax(?float $priceWithoutTax): self
{
$this->priceWithoutTax = $priceWithoutTax;
return $this;
}
public function getStoredTotalPriceWithoutTax(): ?float
{
return $this->storedTotalPriceWithoutTax;
}
public function setStoredTotalPriceWithoutTax(float $storedTotalPriceWithoutTax): self
{
$this->storedTotalPriceWithoutTax = $storedTotalPriceWithoutTax;
return $this;
}
public function getIsUpdatedPrice(): ?bool
{
return $this->isUpdatedPrice;
}
public function setIsUpdatedPrice(?bool $isUpdatedPrice): self
{
$this->isUpdatedPrice = $isUpdatedPrice;
return $this;
}
public function getNextPayment(): ?\DateTimeInterface
{
return $this->nextPayment;
}
public function setNextPayment(?\DateTimeInterface $nextPayment): self
{
$this->nextPayment = $nextPayment;
return $this;
}
/**
* @deprecated
*/
public function getNotAcceptedScanEnveloppe(): ?bool
{
return $this->notAcceptedScanEnveloppe;
}
/**
* @deprecated
*/
public function setNotAcceptedScanEnveloppe(?bool $notAcceptedScanEnveloppe): self
{
$this->notAcceptedScanEnveloppe = $notAcceptedScanEnveloppe;
return $this;
}
public function getInvoiceGenerationDate(): ?\DateTimeInterface
{
return $this->invoiceGenerationDate;
}
public function setInvoiceGenerationDate(?\DateTimeInterface $invoiceGenerationDate): self
{
$this->invoiceGenerationDate = $invoiceGenerationDate;
return $this;
}
public function getOrganization(): ?Organization
{
return $this->organization;
}
public function setOrganization(?Organization $organization): self
{
$this->organization = $organization;
return $this;
}
public function getMonthNumber(): ?int
{
return $this->monthNumber;
}
public function setMonthNumber(?int $monthNumber): self
{
$this->monthNumber = $monthNumber;
return $this;
}
public function getAuthorizedReccurency(): ?array
{
return $this->authorizedReccurency;
}
public function setAuthorizedReccurency(?array $authorizedReccurency): self
{
$this->authorizedReccurency = $authorizedReccurency;
return $this;
}
public function getIsCancelUsingOffer(): ?bool
{
return $this->isCancelUsingOffer;
}
public function setIsCancelUsingOffer(?bool $isCancelUsingOffer): self
{
$this->isCancelUsingOffer = $isCancelUsingOffer;
return $this;
}
public function getIsDigipackOffer(): ?bool
{
return $this->isDigipackOffer;
}
public function setIsDigipackOffer(?bool $isDigipackOffer): self
{
$this->isDigipackOffer = $isDigipackOffer;
return $this;
}
public function getDeadlineOffered(): ?int
{
return $this->deadlineOffered;
}
public function setDeadlineOffered(?int $deadlineOffered): self
{
$this->deadlineOffered = $deadlineOffered;
return $this;
}
public function getPrepaidMonths(): ?int
{
return $this->prepaidMonths;
}
public function setPrepaidMonths(int $prepaidMonths): self
{
$this->prepaidMonths = $prepaidMonths;
return $this;
}
/**
* @return Collection<int, Item>
*/
public function getIncludedProducts(): Collection
{
return $this->includedProducts;
}
/**
* @Groups({"read_subscription", "read_organization"})
*
* @return array <int, Service>
*/
public function getIncludedServices(): array
{
return array_values($this->services->filter(function (Service $service) {
return Service::INCLUDED === $service->getType();
})->toArray());
}
/**
* @Groups({"read_subscription", "read_organization"})
*
* @return array<int, Service>
*/
public function getOptionalServices(): array
{
return array_values($this->services->filter(function (Service $service) {
return Service::OPTIONAL === $service->getType();
})->toArray());
}
/**
* @return Collection<int, Service>
*/
public function getServices(): Collection
{
return $this->services;
}
public function addService(Service $service): self
{
if (!$this->services->contains($service)) {
$this->services->add($service);
$service->setSubscription($this);
}
$_SESSION['hasChanged'] = 1;
if (!isset($_SESSION['typeChanged'])) {
$_SESSION['typeChanged'] = 'add';
} elseif ('remove' === $_SESSION['typeChanged']) {
$_SESSION['typeChanged'] = 'mixte';
}
if (ProductKeyEnum::SCAN_ENVELOPPE === $service->getProduct()->getUniqueKey()) {
$_SESSION['addScanEnveloppe'] = 1;
}
return $this;
}
public function removeService(Service $service): self
{
if ($this->services->contains($service)) {
$this->services->removeElement($service);
}
if (ProductKeyEnum::SCAN_ENVELOPPE === $service->getProduct()->getUniqueKey()) {
$_SESSION['isRemoved'] = 1;
}
$_SESSION['hasChanged'] = 1;
if (!isset($_SESSION['typeChanged'])) {
$_SESSION['typeChanged'] = 'remove';
} elseif ('add' === $_SESSION['typeChanged']) {
$_SESSION['typeChanged'] = 'mixte';
}
return $this;
}
public function isHasBlockedService(): bool
{
return $this->hasBlockedService;
}
public function hasBlockedService(): bool
{
return $this->hasBlockedService;
}
public function updateBlockedService(bool $hasBlockedService): self
{
$this->hasBlockedService = $hasBlockedService;
return $this;
}
public function addProduct(Product $product): self
{
$service = new Service();
$service->setProduct($product);
$service->setSubscription($this);
$this->addService($service);
return $this;
}
public function getProductsIds(): array
{
$productsIds = [];
foreach ($this->getIncludedServices() as $service) {
$productsIds[] = $service->getProduct()->getId();
}
return $productsIds;
}
public function getDocumentRecoveryDelay(): ?\DateTimeInterface
{
return $this->documentRecoveryDelay;
}
public function setDocumentRecoveryDelay(?\DateTimeInterface $documentRecoveryDelay): self
{
$this->documentRecoveryDelay = $documentRecoveryDelay;
return $this;
}
/**
* @Groups({"read_subscription", "read_organization"})
*/
public function getBlockingReason(): array
{
$organization = $this->getOrganization();
if (null === $organization) {
return [];
}
$isUnpaid = OrganizationStatusPayedEnum::UNPAID === $organization->getStatusInvoicePayed();
return SubscriptionUtils::getBlockingReason($this, $isUnpaid);
}
public function setPack(?Pack $pack): self
{
// unset the owning side of the relation if necessary
if (null === $pack && null !== $this->pack) {
$this->pack->setSubscription(null);
}
// set the owning side of the relation if necessary
if (null !== $pack && $pack->getSubscription() !== $this) {
$pack->setSubscription($this);
}
$this->pack = $pack;
return $this;
}
public function create(array $data): self
{
$self = new self();
$self->active = $data['active'] ?? true;
$self->createdAt = $data['createdAt'] ?? new \DateTime();
$self->discountWithoutTax = $data['discountWithoutTax'] ?? null;
$self->invoiceGenerationDate = $data['invoiceGenerationDate'] ?? null;
$self->hasBlockedService = $data['hasBlockedService'] ?? false;
$self->price = $data['price'] ?? null;
$self->priceWithoutTax = $data['priceWithoutTax'] ?? null;
$self->type = $data['type'] ?? 'MONTHLY';
$self->notAcceptedScanEnveloppe = $data['notAcceptedScanEnveloppe'] ?? null;
$self->subscriptionDeposit = $data['subscriptionDeposit'] ?? 0;
$self->nextPayment = $data['nextPayment'] ?? null;
$self->prepaidMonths = $data['prepaidMonths'] ?? 0;
$self->services = $data['services'] ?? null;
if (isset($data['products'])) {
foreach ($data['products'] as $product) {
$self->addService(Service::create(['product' => $product, 'type' => 'included']));
}
}
return $self;
}
public static function createFixture(array $data): self
{
$self = new self();
$self->active = $data['active'] ?? true;
$self->createdAt = $data['createdAt'] ?? new \DateTime();
$self->discountWithoutTax = $data['discountWithoutTax'] ?? null;
$self->invoiceGenerationDate = $data['invoiceGenerationDate'] ?? null;
$self->hasBlockedService = $data['hasBlockedService'] ?? false;
$self->price = $data['price'] ?? null;
$self->priceWithoutTax = $data['priceWithoutTax'] ?? null;
$self->type = $data['type'] ?? 'MONTHLY';
$self->notAcceptedScanEnveloppe = $data['notAcceptedScanEnveloppe'] ?? null;
$self->subscriptionDeposit = $data['subscriptionDeposit'] ?? 0;
$self->nextPayment = $data['nextPayment'] ?? null;
$self->prepaidMonths = $data['prepaidMonths'] ?? 0;
$self->organization = $data['organization'] ?? null;
if (isset($data['products'])) {
foreach ($data['products'] as $product) {
$self->addService(Service::create(['product' => $product, 'type' => 'included']));
}
}
return $self;
}
public function removeExpiredTrialServices(): void
{
$services = $this->getServices();
/** @var Service $service */
foreach ($services as $service) {
if (Service::OPTIONAL === $service->getType()) {
if ($service->expiredAndMustToBeRemove() && !$service->isRemoveAfterTrial()) {
$service->stopTrial();
}
if ($service->expiredAndMustToBeRemove() && $service->isRemoveAfterTrial()) {
$this->removeService($service);
}
}
}
}
// create function to check if the subscription have a service with product uniquekey is a param
public function hasServiceWithProductKey(string $productKey): bool
{
$services = $this->getServices();
foreach ($services as $service) {
if ($service->getProduct()->getUniqueKey() === $productKey) {
return true;
}
}
return false;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): Subscription
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getCommercialGestures(): Collection
{
return $this->commercialGestures;
}
public function addCommercialGesture(CommercialGesture $commercialGesture): self
{
if (!$this->commercialGestures->contains($commercialGesture)) {
$this->commercialGestures->add($commercialGesture);
$commercialGesture->setSubscription($this);
}
return $this;
}
public function removeServiceFromProduct(Product $product): self
{
$services = $this->getServices();
foreach ($services as $service) {
if ($service->getProduct() === $product) {
$this->removeService($service);
}
}
return $this;
}
public function removeProduct(Product $product): self
{
$this->removeServiceFromProduct($product);
return $this;
}
public function removeServiceByType(string $serviceType): self
{
$services = $this->getServices();
foreach ($services as $service) {
if ($service->getType() === $serviceType) {
$this->removeService($service);
}
}
return $this;
}
public function getServiceStoreAddition()
{
$services = $this->getServices();
foreach ($services as $service) {
if (Service::STORE_ADDITION === $service->getType()) {
return $service;
}
}
return null;
}
public function totalPriceWithoutTax(): float
{
$price = $this->getPriceWithoutTax() ?? .0;
foreach ($this->getOptionalServices() as $service) {
if (!$service->isTrial()) {
$price += $service->getPriceWithoutTax();
}
}
$storeAdditionService = $this->getServiceStoreAddition();
if (null !== $storeAdditionService) {
$price += $storeAdditionService->getPriceWithoutTax();
}
$price -= (float) $this->getDiscountWithoutTax();
if ($price < 0) {
$price = 0;
}
return $price;
}
public function haveDomiciliation(): bool
{
return $this->hasServiceWithProductKey(ProductKeyEnum::DOMICILIATION_COMMERCIALE);
}
/**
* @Groups({"read_subscription", "read_organization"})
*/
public function getTotalPriceWithoutTax(): float
{
return round($this->totalPriceWithoutTax(), 2);
}
/**
* @Groups({"read_subscription", "read_organization"})
*/
public function getTotalPriceWithTax(): float
{
return round($this->totalPriceWithoutTax() * 1.2, 2);
}
}