<?php
namespace App\Entity;
use AdScore\Common\Crypt\Idfuscator;
use AdScore\Common\Gearboy\InvokerFactory;
use App\Component\Serializer\Normalizer\ProxyAwareObjectNormalizer;
use App\Entity\Exception\TransactionAmountException;
use App\HelperFunctions;
use DateTime;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Exception;
use Symfony\Component\Serializer\Annotation as SA;
/**
* Account
*
* @ORM\Table(name="accounts", indexes={@ORM\Index(name="verified", columns={"verified"})})
* @ORM\Entity(repositoryClass="App\Repository\AccountRepository")
*/
class Account extends Entity
{
public const BADGE_INFO_DEFAULTS = [
'enabled' => false,
'name' => null,
'email' => null,
'phone_number' => null,
'address' => null,
'website' => null
];
public const SERIALIZATION_CONTEXT = [
'general' => ['groups' => ['account_general']],
'details' => ['groups' => ['account_general', 'account_details']],
'badge' => ['groups' => ['badge']],
'search' => ['groups' => ['account_general', 'account_search'], ProxyAwareObjectNormalizer::RESOLVE_PROXY_CLASSES => [Role::class, Account::class, UserInteractive::class]],
'admin_details' => ['groups' => ['account_general', 'account_details', 'account_search', 'account_admin_details'],
ProxyAwareObjectNormalizer::RESOLVE_PROXY_CLASSES => [Role::class, Account::class, UserInteractive::class, Transaction::class, Zone::class]
],
];
public const RUNOUT_LOCK_TIME = 60 * 60 * 24 * 7;
const RUNOUT_LOCK = [
'dashboard' => 'dashboard',
'account' => 'account'
];
/**
* @var int
*
* @ORM\Column(name="id", type="bigint", nullable=false, options={"unsigned"=true})
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
* @SA\Groups({"account_general", "user_search"})
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255, nullable=false, options={"comment"="Company or entity name"})
* @SA\Groups({"account_general", "badge", "user_search"})
*/
private $name;
/**
* @var DateTime
*
* @ORM\Column(name="created", type="datetime", nullable=false, options={"default"="CURRENT_TIMESTAMP"})
* @SA\Groups({"account_general", "badge"})
*/
private $created = null;
/**
* @var string
*
* @ORM\Column(name="status", type="string", length=0, nullable=false, options={"default"="inactive"})
* @SA\Groups({"account_general", "badge"})
*/
private $status = 'inactive';
/**
* @var string
*
* @ORM\Column(name="time_zone", type="string", length=64, nullable=false, options={"default"="UTC"})
* @SA\Groups({"account_details"})
*/
private $timeZone = 'UTC';
/**
* @var string
*
* @ORM\Column(name="fdow", type="string", length=0, nullable=false, options={"default"="monday"})
* @SA\Groups({"account_details"})
*/
private $fdow = 'monday';
/**
* @var string
*
* @ORM\Column(name="billing_method", type="string", length=0, nullable=false, options={"default"="prepaid"})
* @SA\Groups({"account_details"})
*/
private $billingMethod = 'prepaid';
/**
* @var string|null
*
* @ORM\Column(name="billing_details", type="text", length=65535, nullable=true)
* @SA\Groups({"account_details"})
*/
private $billingDetails;
/**
* @var string|null
*
* @ORM\Column(name="price", type="decimal", precision=8, scale=6, nullable=true, options={"default"=0.000150})
* @SA\Groups({"account_details"})
*/
private $price = 0.000150;
/**
* @var string|null
*
* @ORM\Column(name="external_sub_id_url", type="string", length=255, nullable=true)
* @SA\Groups({"account_details"})
*/
private $externalSubIdUrl = null;
/**
* @var object|null
*
* @ORM\Column(name="meta", type="json", nullable=true)
*/
private $meta;
/**
* @var int
*
* @ORM\Column(name="verified", type="bigint", nullable=false, options={"unsigned"=true})
* @SA\Groups({"account_general"})
*/
private $verified = '0';
/**
* @var object|null
*
* @ORM\Column(name="badge_info", type="json", nullable=true)
*/
private $badgeInfo;
/**
* @var ArrayCollection|Role[]
*
* @SA\SerializedName("role")
* @ORM\OneToMany(targetEntity="Role", mappedBy="account")
* @SA\Ignore
*/
private $roles;
/**
* @var ArrayCollection|Zone[]
*
* @SA\Groups({"account_admin_details"})
* @ORM\OneToMany(targetEntity="Zone", mappedBy="account")
*/
private $zones;
/**
* @var ArrayCollection|GwocDomain[]
*
* @SA\Ignore
* @ORM\OneToMany(targetEntity="GwocDomain", mappedBy="account")
*/
private $gwocDomains;
/**
* @var ArrayCollection|User[]
*
* @ORM\ManyToMany(targetEntity="User", fetch="EAGER")
* @ORM\JoinTable(name="roles",
* joinColumns={@ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
* @SA\Ignore
*/
private $users;
/**
* @var AccountRunout
*
* @ORM\OneToOne(targetEntity="AccountRunout", fetch="EAGER")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="id", referencedColumnName="id")
* })
*/
private $runout;
/**
* @var ArrayCollection|Transaction[]
*
* @ORM\OneToMany(targetEntity="Transaction", mappedBy="account")
* @SA\Groups({"account_admin_details"})
*/
private $transactions;
private $transactionAmount;
/**
* @var array|null
*/
private $trafficData = null;
/**
* @var float
*/
private $trafficAmount = null;
/**
* @var float
*/
private $balance = null;
/**
* @var bool
*/
private $hasTraffic = false;
/**
* @var int
*/
private $ownerId;
/**
* @internal Tthis is not being called for records returned from a database!
*/
public function __construct()
{
$this->roles = new ArrayCollection();
$this->zones = new ArrayCollection();
$this->users = new ArrayCollection();
$this->gwocDomains = new ArrayCollection();
$this->transactions = new ArrayCollection();
$this->created = new DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getCreated(): ?DateTimeInterface
{
return $this->created;
}
public function setCreated(DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): self
{
$this->status = $status;
return $this;
}
public function getTimeZone(): ?string
{
return $this->timeZone;
}
public function setTimeZone(string $timeZone): self
{
$this->timeZone = $timeZone;
return $this;
}
public function getFdow(): ?string
{
return $this->fdow;
}
public function setFdow(string $fdow): self
{
$this->fdow = $fdow;
return $this;
}
public function getBillingDetails(): ?string
{
return is_null($this->billingDetails) ? null : trim($this->billingDetails);
}
public function setBillingDetails(?string $billingDetails): self
{
if (!is_null($billingDetails)) {
$this->billingDetails = trim($billingDetails);
} else {
$this->billingDetails = null;
}
return $this;
}
public function getBillingMethod(): ?string
{
return $this->billingMethod;
}
public function setBillingMethod(string $billingMethod): self
{
$this->billingMethod = $billingMethod;
return $this;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(?float $price): self
{
$this->price = $price;
return $this;
}
public function getExternalSubIdUrl(): ?string
{
return $this->externalSubIdUrl;
}
public function setExternalSubIdUrl(?string $externalSubIdUrl): self
{
if (empty(trim($externalSubIdUrl))) {
$this->externalSubIdUrl = null;
} else {
$this->externalSubIdUrl = $externalSubIdUrl;
}
return $this;
}
/**
* @return object
* @SA\Groups({"account_details"})
*/
public function getMeta(): ?object
{
return $this->meta;
}
public function setMeta(?object $meta): self
{
$this->meta = $meta;
return $this;
}
public function isVerified(): bool
{
return (bool)$this->verified;
}
public function setVerified(string $verified): self
{
$this->verified = $verified;
return $this;
}
/**
* @return object
* @throws Exception
* @SA\Groups({"account_details", "badge"})
*/
public function getBadgeInfo(): object
{
return HelperFunctions::setStdDefaults((object)$this->badgeInfo, self::BADGE_INFO_DEFAULTS);
}
/**
* @param object|null $badgeInfo
* @return $this
* @throws Exception
*/
public function setBadgeInfo(?object $badgeInfo): self
{
$this->badgeInfo = HelperFunctions::setStdDefaults((object)$badgeInfo, (array) $this->getBadgeInfo());
return $this;
}
/**
* @return ArrayCollection<int, Role>|Role[]
*/
public function getRoles(): Collection
{
return $this->roles;
}
public function addRole(Role $role): self
{
if (!$this->roles->contains($role)) {
$this->roles[] = $role;
$role->setAccount($this);
}
return $this;
}
public function removeRole(Role $role): self
{
if ($this->roles->removeElement($role)) {
// set the owning side to null (unless already changed)
if ($role->getAccount() === $this) {
$role->setAccount(null);
}
}
return $this;
}
/**
* @return Collection<int, Zone>
*/
public function getZones(): Collection
{
return $this->zones;
}
public function addZone(Zone $zone): self
{
if (!$this->zones->contains($zone)) {
$this->zones[] = $zone;
$zone->setAccount($this);
}
return $this;
}
public function removeZone(Zone $zone): self
{
if ($this->zones->removeElement($zone)) {
// set the owning side to null (unless already changed)
if ($zone->getAccount() === $this) {
$zone->setAccount(null);
}
}
return $this;
}
/**
* @return Collection<int, GwocDomain>
*/
public function getGwocDomains(): Collection
{
return $this->gwocDomains;
}
public function addGwocDomain(GwocDomain $gwocDomain): self
{
if (!$this->gwocDomains->contains($gwocDomain)) {
$this->gwocDomains[] = $gwocDomain;
$gwocDomain->setAccount($this);
}
return $this;
}
public function removeGwocDomain(GwocDomain $gwocDomain): self
{
if ($this->gwocDomains->removeElement($gwocDomain)) {
// set the owning side to null (unless already changed)
if ($gwocDomain->getAccount() === $this) {
$gwocDomain->setAccount(null);
}
}
return $this;
}
public function getUsers(?string $type = null): array
{
if (empty($type) || !in_array($type, User::TYPE)) {
$criteria = Criteria::expr()->eq('status', self::STATUS['active']);
} else {
$criteria = Criteria::expr()->andX(
Criteria::expr()->eq('type', $type),
Criteria::expr()->eq('status', self::STATUS['active'])
);
}
return array_values($this->users->matching(Criteria::create()->where($criteria))->toArray());
}
/**
* @SA\SerializedName("users")
* @SA\Groups({"account_search"})
*/
public function getInteractiveUsers(): array
{
return $this->getUsers(User::TYPE['interactive']);
}
/**
* @SA\Groups({"account_general"})
* @return string|null
* @throws Exception
*/
public function getBadgeHash(): ?string
{
if (!$this->getBadgeInfo()->enabled) {
return null;
}
$encoder = new Idfuscator('account_badge');
return $encoder->encode($this->getId(), 0);
}
/**
* @return DateTimeInterface|null
* @SA\Groups({"account_details"})
*/
public function getRunoutCreated(): ?DateTimeInterface
{
return is_object($this->runout) ? $this->runout->getCreated() : null;
}
/**
* @return float|null
* @SA\Groups({"account_details"})
*/
public function getRunoutBalance(): ?float
{
return is_object($this->runout) ? $this->runout->getRunout() : null;
}
/**
* @return string|null
* @SA\Groups({"account_details"})
*/
public function getRunoutLock(): ?string
{
if (is_null($this->runout)) {
return null;
}
$runoutTime = $this->runout->getCreated()->getTimestamp() + self::RUNOUT_LOCK_TIME;
if ($runoutTime < time()) {
return self::RUNOUT_LOCK['account'];
}
return self::RUNOUT_LOCK['dashboard'];
}
/**
* @return mixed
* @SA\Groups({"account_details"})
*/
public function getTransactionAmount(): ?float
{
return round($this->transactionAmount, 6);
}
/**
* @param mixed $transactionAmount
*/
public function setTransactionAmount(?float $transactionAmount): self
{
$this->transactionAmount = $transactionAmount;
return $this;
}
/**
* @return Transaction[]|ArrayCollection
*/
public function getTransactions(): Collection
{
return $this->transactions;
}
/**
* @param Transaction[]|ArrayCollection $transactions
* @return Account
*/
public function setTransactions(Collection $transactions)
{
$this->transactions = $transactions;
return $this;
}
/**
* @return float
* @SA\Groups({"account_details"})
*/
public function getTrafficAmount(): ?float
{
return $this->trafficAmount;
}
/**
* @return float
* @SA\Groups({"account_details"})
*/
public function getBalance(): ?float
{
return $this->balance;
}
/**
* @return bool
* @SA\Groups({"account_details"})
* @SA\SerializedName("has_traffic")
*/
public function hasTraffic(): bool
{
return isset($this->trafficData[0]['cost']);
}
public function fetchTrafficData(): void
{
if (is_null($this->id)) {
throw new RuntimeException('Cannot fetch traffic data for non-existent account');
}
$invoker = InvokerFactory::get('traffic_local_copy');
$this->trafficData = $invoker->query('SELECT account_id, sumMerge(cost) AS cost FROM cluster_account_balance WHERE account_id = :account_id GROUP BY account_id', ['account_id' => $this->id]);
$this->trafficAmount = round(($this->trafficData[0]['cost'] ?? 0) / 1000000, 6);
if (is_null($this->transactionAmount)) {
throw new TransactionAmountException();
}
$this->balance = $this->transactionAmount - $this->trafficAmount;
$this->hasTraffic = true;
}
public function setOwnerId(int $ownerId)
{
$this->ownerId = $ownerId;
}
/**
* @return int
* @SA\Groups({"account_details"})
*/
public function getOwnerId(): int
{
return $this->ownerId;
}
}