<?php
/**
* @license SILK SOFTWARE HOUSE SP Z O O
*/
namespace App\Entity;
use App\Entity\Traits\IdTrait;
use App\Entity\Traits\TimePreUpdateTrait;
use App\Entity\Traits\TimeTrait;
use App\Model\Filter\MeasurementFilter;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class Device.
*
* @ORM\Table("devices")
* @ORM\Entity(repositoryClass="App\Repository\DeviceRepository")
* @ORM\HasLifecycleCallbacks()
*
* @UniqueEntity(fields={"serialNumber"})
*/
class Device
{
use IdTrait;
use TimeTrait;
use TimePreUpdateTrait;
/**
* Serial number.
*
* @var string
*
* @ORM\Column(name="serial_number", type="string", nullable=false, options={"comment": "Serial number."})
*/
private $serialNumber;
/**
* Customer.
*
* @var Customer|null
*
* @ORM\ManyToOne(targetEntity="App\Entity\Customer", inversedBy="devices")
* @ORM\JoinColumn(name="customer_id", nullable=true)
*/
private $customer;
/**
* Measurements.
*
* @var Measurement[]|ArrayCollection
*
* @ORM\OneToMany(targetEntity="App\Entity\Measurement", mappedBy="device")
*
* @ORM\OrderBy({"finishedAt" = "DESC"})
*/
private $measurements;
/**
* @var array|null
*/
private $measurementIds;
/**
* Assignment ends at.
*
* @var DateTime|null
*
* @ORM\Column(name="active_to", type="datetime", nullable=true, options={"comment": "Assignment ends at."})
*/
private $assignmentEndsAt;
/**
* Is used.
*
* @var bool
*
* @ORM\Column(name="is_used", type="boolean", nullable=false, options={"comment": "Did used marked this devices as used.", "default": true})
*/
private $isUsed = true;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $calibratedAt;
/**
* Channels limit.
*
* @var int|null
*
* @ORM\Column(name="channels_limit", type="integer", options={"comment": "Channels limit.", "default": 12}, nullable=true)
*/
private $channelsLimit = 12;
/**
* Name assigned to this device.
*
* @var string|null
*
* @ORM\Column(name="name", type="text", nullable=true, options={"comment": "Name assigned to this device."})
*/
private $name;
/**
* Device user settings.
*
* @var UserSetting[]|ArrayCollection
*
* @ORM\ManyToMany(targetEntity="App\Entity\UserSetting", mappedBy="usedDevices")
*/
private $userSetting;
/**
* @ORM\OneToMany(targetEntity="App\Entity\DataSample", mappedBy="device")
* @ORM\OrderBy({"id" = "asc"})
*/
private $dataSample;
/**
* @ORM\OneToMany(targetEntity="App\Entity\RecipeToSync", mappedBy="device", cascade={"remove"})
*/
private $recipeToSyncs;
/**
* @ORM\OneToMany(targetEntity="App\Entity\DeviceCommand", mappedBy="device")
*/
private $deviceCommands;
/**
* @ORM\OneToOne(targetEntity="App\Entity\DeviceLastCommunication", mappedBy="device", cascade={"persist", "remove"})
* @ORM\OrderBy({"id" = "DESC"})
*/
private $deviceLastCommunication;
/**
* @ORM\Column(type="string", nullable=true, length=255)
*
* @Assert\Email(
* message = "The email '{{ value }}' is not a valid email."
* )
*/
private $email;
/**
* @ORM\Column(type="datetime", name="latest_battery_level_notification_at", nullable=true)
*/
private $latestBatteryLevelNotificationAt;
/**
* Device constructor.
*/
public function __construct()
{
$this->measurements = new ArrayCollection();
$this->userSetting = new ArrayCollection();
$this->dataSample = new ArrayCollection();
$this->recipeToSyncs = new ArrayCollection();
$this->deviceCommands = new ArrayCollection();
}
/**
* Get serial number.
*
* @return string
*/
public function getSerialNumber(): string
{
return $this->serialNumber;
}
/**
* Set serial number.
*
* @param string $serialNumber
*
* @return Device
*/
public function setSerialNumber(string $serialNumber): Device
{
$this->serialNumber = $serialNumber;
return $this;
}
/**
* Get customer.
*
* @return Customer|null
*/
public function getCustomer(): ?Customer
{
return $this->customer;
}
/**
* Set customer.
*
* @param Customer|null $customer
*
* @return Device
*/
public function setCustomer(?Customer $customer): Device
{
if ((!$this->customer && $customer) || ($this->customer && !$customer)
|| ($this->customer && $customer && $customer->getId() !== $this->customer->getId())) {
$this->isUsed = true;
}
$this->customer = $customer;
return $this;
}
/**
* Get active measurements.
*
* @return Measurement[]|ArrayCollection
*/
public function getActiveMeasurements(): ArrayCollection
{
$result = new ArrayCollection();
foreach ($this->measurements as $measurement) {
if (!$measurement->getIsFinished()) {
$result->add($measurement);
}
}
return $result;
}
/**
* Get measurements.
*
* @return Measurement[]|ArrayCollection
*/
public function getMeasurements()
{
return $this->measurements;
}
/**
* Get array of measurements id.
*
* The value is cached.
*
* @return array|null
*/
public function getSortedMeasurmentsIdsArray()
{
if (!isset($this->measurementIds)) {
$measurements = $this->getMeasurements();
if ($measurements->isEmpty()) {
return [];
}
$measurementIds = array_map(function ($measurement) {
return $measurement->getId();
}, $measurements->toArray());
sort($measurementIds);
$this->measurementIds = $measurementIds;
}
return $this->measurementIds;
}
/**
* Prime the relative-name lookup when measurements were loaded by an
* optimized overview query instead of through this entity collection.
*
* @param Measurement[] $measurements
*/
public function cacheSortedMeasurementIds(iterable $measurements): void
{
$this->measurementIds = [];
foreach ($measurements as $measurement) {
$this->measurementIds[] = $measurement->getId();
}
sort($this->measurementIds);
}
/**
* Prime the relative-name lookup from an inexpensive scalar ID query.
*
* @param int[] $measurementIds
*/
public function cacheSortedMeasurementIdsArray(iterable $measurementIds): void
{
$this->measurementIds = array_values(array_unique(array_map('intval', is_array($measurementIds)
? $measurementIds
: iterator_to_array($measurementIds))));
sort($this->measurementIds);
}
/**
* Get active measurements.
*
* @param MeasurementFilter $filters
*
* @return Measurement[]|ArrayCollection
*/
public function getInactiveMeasurements(MeasurementFilter $filters): ArrayCollection
{
$result = new ArrayCollection();
foreach ($this->measurements as $measurement) {
if (!$measurement->getIsFinished()
|| ($filters->getMeasurementId() && (int) $filters->getMeasurementId() !== $measurement->getId())
|| $filters->getIsDateToLowerThan($measurement->getStartedAt())
|| $filters->getIsDateFromGreaterThan($measurement->getFinishedAt())) {
continue;
}
$result->add($measurement);
}
return $this->sortMeasurementsByArchiveTime($result);
}
/**
* Keep archive ordering deterministic even when older measurements are inserted later.
*
* @param ArrayCollection|Measurement[] $measurements
*
* @return ArrayCollection
*/
private function sortMeasurementsByArchiveTime(ArrayCollection $measurements): ArrayCollection
{
$iterator = $measurements->getIterator();
$iterator->uasort(function (Measurement $left, Measurement $right): int {
$comparison = $right->getFinishedAt() <=> $left->getFinishedAt();
if (0 !== $comparison) {
return $comparison;
}
return $right->getStartedAt() <=> $left->getStartedAt();
});
return new ArrayCollection(iterator_to_array($iterator));
}
/**
* Get is active.
*
* @return bool
*/
public function getIsActive(): bool
{
return $this->getActiveMeasurements()->count() > 0;
}
/**
* Get active assignment.
*
* @return Customer|null
*
* @throws \Exception
*/
public function getActiveAssignment(): ?Customer
{
if (null === $this->assignmentEndsAt || $this->assignmentEndsAt > new DateTime()) {
return $this->customer;
}
return null;
}
/**
* Get last measurement.
*
* @return Measurement|null
*/
public function getLastMeasurement(): ?Measurement
{
foreach (krsort($this->measurements) as $measurement) {
if (true === $measurement->getSetting()->isDeleted()) {
continue;
}
return $measurement;
}
return null;
}
/**
* Get first measurement.
*
* @return Measurement|null
*/
public function getFirstMeasurement(): ?Measurement
{
foreach ($this->measurements as $measurement) {
// im sorry but | null must be checked
if (null !== $measurement->getSetting()) {
if (true === $measurement->getSetting()->isDeleted()) {
continue;
}
}
return $measurement;
}
return null;
}
/**
* Returns duration in days.
*
* @return int|null
*
* @throws \Exception
*/
public function getDuration(): ?int
{
/** @var Measurement $lastMeasurement */
$lastMeasurement = $this->measurements->last();
if (!$lastMeasurement) {
return null;
}
$endsAt = $lastMeasurement->getFinishedAt() ?? new DateTime();
$startedAt = $lastMeasurement->getStartedAt() ?? $lastMeasurement->getCreatedAt();
return (int) $endsAt->diff($startedAt)->format('%a') + 1;
}
/**
* To string.
*
* @return string
*/
public function __toString()
{
return $this->serialNumber ?? '{{ not-serialized-device }}';
}
/**
* Get assignment ends at.
*
* @return DateTime|null
*/
public function getAssignmentEndsAt(): ?DateTime
{
return $this->assignmentEndsAt;
}
/**
* Set assignment ends at.
*
* @param DateTime|null $assignmentEndsAt
*
* @return Device
*/
public function setAssignmentEndsAt(?DateTime $assignmentEndsAt): Device
{
$this->assignmentEndsAt = $assignmentEndsAt;
return $this;
}
/**
* Get is used.
*
* @return bool
*/
public function isUsed(): bool
{
return $this->isUsed;
}
/**
* Set is used.
*
* @param bool $isUsed
*
* @return Device
*/
public function setIsUsed(bool $isUsed): Device
{
$this->isUsed = $isUsed;
return $this;
}
/**
* @return DateTimeInterface|null
*/
public function getCalibratedAt(): ?DateTimeInterface
{
return $this->calibratedAt;
}
/**
* @param DateTimeInterface|null $calibratedAt
*
* @return Device
*/
public function setCalibratedAt(?DateTimeInterface $calibratedAt): self
{
$this->calibratedAt = $calibratedAt;
return $this;
}
/**
* Get channels limit.
*
* @return int|null
*/
public function getChannelsLimit(): ?int
{
return $this->channelsLimit;
}
/**
* Set channels limit.
*
* @param int|null $channelsLimit
*
* @return Device
*/
public function setChannelsLimit(?int $channelsLimit): Device
{
$this->channelsLimit = $channelsLimit;
return $this;
}
/**
* Get name.
*
* @param bool $defaultOnNull
*
* @return string|null
*/
public function getName(?bool $defaultOnNull = false): ?string
{
if ($defaultOnNull && empty($this->name)) {
return '{{instrument}} '.$this->getId();
}
return $this->name;
}
/**
* @return string
*/
public function getNameWithSerial(): string
{
return $this->getSerialNumber().': '.$this->getName(true);
}
/**
* Set name.
*
* @param string|null $name
*
* @return Device
*/
public function setName(?string $name): Device
{
$this->name = $name;
return $this;
}
/**
* @return Collection|DataSample[]
*/
public function getDataSample(): Collection
{
return $this->dataSample;
}
/**
* @param DataSample $dataSample
*
* @return Device
*/
public function addDataSample(DataSample $dataSample): self
{
if (!$this->dataSample->contains($dataSample)) {
$this->dataSample[] = $dataSample;
$dataSample->setDevice($this);
}
return $this;
}
/**
* @param DataSample $dataSample
*
* @return Device
*/
public function removeDataSample(DataSample $dataSample): self
{
if ($this->dataSample->contains($dataSample)) {
$this->dataSample->removeElement($dataSample);
// set the owning side to null (unless already changed)
if ($dataSample->getDevice() === $this) {
$dataSample->setDevice(null);
}
}
return $this;
}
/**
* @return Collection|RecipeToSync[]
*/
public function getRecipeToSyncs(): Collection
{
return $this->recipeToSyncs;
}
/**
* @param RecipeToSync $recipeToSync
*
* @return Device
*/
public function addRecipeToSync(RecipeToSync $recipeToSync): self
{
if (!$this->recipeToSyncs->contains($recipeToSync)) {
$this->recipeToSyncs[] = $recipeToSync;
$recipeToSync->setDevice($this);
}
return $this;
}
/**
* @param RecipeToSync $recipeToSync
*
* @return Device
*/
public function removeRecipeToSync(RecipeToSync $recipeToSync): self
{
if ($this->recipeToSyncs->contains($recipeToSync)) {
$this->recipeToSyncs->removeElement($recipeToSync);
// set the owning side to null (unless already changed)
if ($recipeToSync->getDevice() === $this) {
$recipeToSync->setDevice(null);
}
}
return $this;
}
/**
* @return Collection|DeviceCommand[]
*/
public function getDeviceCommands(): Collection
{
return $this->deviceCommands;
}
/**
* @param DeviceCommand $deviceCommand
*
* @return Device
*/
public function addDeviceCommand(DeviceCommand $deviceCommand): self
{
if (!$this->deviceCommands->contains($deviceCommand)) {
$this->deviceCommands[] = $deviceCommand;
$deviceCommand->setDevice($this);
}
return $this;
}
/**
* @param DeviceCommand $deviceCommand
*
* @return Device
*/
public function removeDeviceCommand(DeviceCommand $deviceCommand): self
{
if ($this->deviceCommands->contains($deviceCommand)) {
$this->deviceCommands->removeElement($deviceCommand);
// set the owning side to null (unless already changed)
if ($deviceCommand->getDevice() === $this) {
$deviceCommand->setDevice(null);
}
}
return $this;
}
/**
* @return DeviceLastCommunication|null
*/
public function getDeviceLastCommunication(): ?DeviceLastCommunication
{
return $this->deviceLastCommunication;
}
/**
* @param DeviceLastCommunication $deviceLastCommunication
*
* @return Device
*/
public function setDeviceLastCommunication(DeviceLastCommunication $deviceLastCommunication): self
{
$this->deviceLastCommunication = $deviceLastCommunication;
// set the owning side of the relation if necessary
if ($deviceLastCommunication->getDevice() !== $this) {
$deviceLastCommunication->setDevice($this);
}
return $this;
}
/**
* @return string|null
*/
public function getEmail(): ?string
{
return $this->email;
}
/**
* @param string|null $email
*
* @return Device
*/
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
/**
* @return DateTime|null
*/
public function getLatestBatteryLevelNotificationAt(): ?DateTime
{
return $this->latestBatteryLevelNotificationAt;
}
/**
* @param DateTime|null $latestBatteryLevelNotificationAt
*
* @return Device
*/
public function setLatestBatteryLevelNotificationAt(?DateTime $latestBatteryLevelNotificationAt = null): self
{
$this->latestBatteryLevelNotificationAt = $latestBatteryLevelNotificationAt;
return $this;
}
}