<?php
namespace App\Entity;
use App\Entity\Enum\TransactionStatus;
use App\Entity\Traits\Identifiable;
use App\Entity\Traits\Timestampable;
use App\Form\Admin\Payment\TransactionCashType;
use App\Form\Admin\Payment\TransactionCheckCouponType;
use App\Form\Admin\Payment\TransactionCheckType;
use App\Form\Admin\Payment\TransactionRefundType;
use App\Form\Admin\Payment\TransactionWireType;
use App\Repository\TransactionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
#[ORM\Entity(repositoryClass: TransactionRepository::class)]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn('type', 'string')]
#[ORM\DiscriminatorMap(self::TYPE_CLASSES)]
class Transaction implements CancellableInterface
{
use Identifiable;
use Timestampable;
public const TYPE_DEFAULT = 'default';
public const TYPE_CARD = 'card';
public const TYPE_CARD_SINGLE = 'card_single';
public const TYPE_CARD_MULTI = 'card_multi';
public const TYPE_CHECK = 'check';
public const TYPE_CHECK_COUPON = 'check_coupon';
public const TYPE_CASH = 'cash';
public const TYPE_WIRE = 'wire';
public const TYPE_DEPOSIT_CHECK = 'deposit_check';
public const TYPE_RENTAL_DEPOSIT_CHECK = 'rental_deposit_check';
public const TYPE_DISCOUNT_DEPOSIT_CHECK = 'discount_deposit_check';
public const TYPE_DISCOUNT_RECEIPT = 'discount_receipt';
public const TYPE_TERMINAL = 'terminal';
public const TYPE_REFUND = 'refund';
public const TYPE_CLASSES = [
self::TYPE_DEFAULT => Transaction::class,
self::TYPE_CARD => TransactionCard::class,
self::TYPE_CARD_SINGLE => TransactionCardSingle::class,
self::TYPE_CARD_MULTI => TransactionCardMulti::class,
self::TYPE_CHECK => TransactionCheck::class,
self::TYPE_CHECK_COUPON => TransactionCheckCoupon::class,
self::TYPE_CASH => TransactionCash::class,
self::TYPE_WIRE => TransactionWire::class,
self::TYPE_DEPOSIT_CHECK => TransactionDepositCheck::class,
self::TYPE_RENTAL_DEPOSIT_CHECK => TransactionRentalDepositCheck::class,
self::TYPE_DISCOUNT_DEPOSIT_CHECK => TransactionDiscountDepositCheck::class,
self::TYPE_DISCOUNT_RECEIPT => TransactionDiscountReceipt::class,
self::TYPE_TERMINAL => TransactionTerminal::class,
self::TYPE_REFUND => TransactionRefund::class,
];
public const TYPE_NAMES = [
self::TYPE_DEFAULT => 'Paiement',
self::TYPE_CARD_SINGLE => 'Par carte',
self::TYPE_CARD_MULTI => 'Par carte en 3x',
self::TYPE_CHECK => 'Par chèques',
self::TYPE_CHECK_COUPON => 'Par chèques vacances ou coupons sport ANCV',
self::TYPE_CASH => 'En espèces',
self::TYPE_WIRE => 'Par virement',
self::TYPE_DEPOSIT_CHECK => 'Chèque de caution',
self::TYPE_RENTAL_DEPOSIT_CHECK => 'Chèque de caution (location)',
self::TYPE_DISCOUNT_DEPOSIT_CHECK => 'Chèque de caution (aides à la pratique du sport)',
self::TYPE_DISCOUNT_RECEIPT => 'Justificatif (aides à la pratique du sport)',
self::TYPE_TERMINAL => 'TPE',
self::TYPE_REFUND => 'Remboursement',
];
public const PUBLIC_TYPES = [
self::TYPE_CARD_SINGLE,
self::TYPE_CARD_MULTI,
self::TYPE_CHECK,
self::TYPE_CHECK_COUPON,
self::TYPE_CASH,
self::TYPE_WIRE,
];
public const MANUAL_TYPES = [
self::TYPE_CASH,
self::TYPE_TERMINAL,
self::TYPE_WIRE,
self::TYPE_CHECK,
self::TYPE_CHECK_COUPON,
self::TYPE_DEPOSIT_CHECK,
self::TYPE_RENTAL_DEPOSIT_CHECK,
self::TYPE_DISCOUNT_DEPOSIT_CHECK,
self::TYPE_REFUND,
];
public const FORM_CLASSES = [
self::TYPE_CHECK => TransactionCheckType::class,
self::TYPE_CHECK_COUPON => TransactionCheckCouponType::class,
self::TYPE_CASH => TransactionCashType::class,
self::TYPE_TERMINAL => TransactionCashType::class,
self::TYPE_WIRE => TransactionWireType::class,
self::TYPE_DEPOSIT_CHECK => TransactionCheckType::class,
self::TYPE_RENTAL_DEPOSIT_CHECK => TransactionCheckType::class,
self::TYPE_DISCOUNT_DEPOSIT_CHECK => TransactionCheckType::class,
self::TYPE_REFUND => TransactionRefundType::class,
];
#[ORM\Column(type: 'float')]
private $amount;
#[ORM\Column(type: 'float')]
private $amountPayed = 0;
#[ORM\Column(type: 'string', length: 20, enumType: TransactionStatus::class)]
private $status = TransactionStatus::Created;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $comment;
#[ORM\ManyToOne(targetEntity: Order::class, inversedBy: 'transactions')]
#[ORM\JoinColumn(nullable: false)]
private $order;
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
private $parent;
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
private $children;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $cancelReason;
public function __construct()
{
$this->children = new ArrayCollection();
}
public function getAmount(): ?float
{
return $this->amount;
}
public function setAmount(float $amount): self
{
$this->amount = $amount;
return $this;
}
public function getAmountPayed(bool $checkMultiCardWaiting = false): ?float
{
if ($checkMultiCardWaiting &&
$this instanceof TransactionCardMulti &&
$this->getStatus() === TransactionStatus::Waiting &&
$this->getCycle() > 1) {
return $this->amount;
}
return $this->amountPayed;
}
public function setAmountPayed(float $amountPayed): self
{
$this->amountPayed = $amountPayed;
return $this;
}
public function getStatus(): ?TransactionStatus
{
return $this->status;
}
public function setStatus(TransactionStatus $status): self
{
$this->status = $status;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): self
{
$this->comment = $comment;
return $this;
}
public function getOrder(): ?Order
{
return $this->order;
}
public function setOrder(?Order $order): self
{
$this->order = $order;
return $this;
}
public static function createFromType(string $type): self
{
if (!array_key_exists($type, self::TYPE_CLASSES)) {
throw new InvalidArgumentException(
sprintf('type must be one of "%s"', implode(', ', array_keys(self::TYPE_CLASSES)))
);
}
return new (self::TYPE_CLASSES[$type]);
}
public function getType(?bool $slug = false): string
{
if ($slug) {
return array_search(get_class($this), self::TYPE_CLASSES);
}
return self::TYPE_NAMES[array_search(get_class($this), self::TYPE_CLASSES)];
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
return $this;
}
/**
* @return Collection<int, self>
*/
public function getChildren(): Collection
{
return $this->children;
}
public function addChild(self $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(self $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getName(): string
{
return self::getNameByClass(static::class) ?? '';
}
public static function getNameByClass($class): ?string
{
$key = array_search($class, self::TYPE_CLASSES, true);
if ($key && (self::TYPE_NAMES[$key] ?? null)) {
return self::TYPE_NAMES[$key];
}
return null;
}
public function getCancelReason(): ?string
{
return $this->cancelReason;
}
public function setCancelReason(?string $cancelReason): self
{
$this->cancelReason = $cancelReason;
return $this;
}
public static function getPublicClasses(): array
{
$publicTypes = self::PUBLIC_TYPES;
return array_filter(self::TYPE_CLASSES, static fn($key) => in_array($key, $publicTypes), ARRAY_FILTER_USE_KEY);
}
}