<?php declare(strict_types=1);
namespace App\Entity\Project;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\BooleanFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Controller\RemoveDeveloperFromProjectController;
use App\Controller\RemoveManagerFromProjectController;
use App\Dto\{ProjectInput,
ProjectOutput,
TimeEntryFixInput,
TimeEntryFixOutput};
use App\Entity\Contractor;
use App\Entity\Plan\Month;
use App\Entity\Plan\WeekPlan as Plan;
use App\Entity\TimeControl\Task;
use App\Entity\TimeControl\TimeEntry;
use App\Entity\Unit;
use App\Entity\User;
use App\Interfaces\SluggableInterface;
use App\Interfaces\UuidKeyInterface;
use App\Traits\SlugAsIdTrait;
use App\Traits\TimestampableEntity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource(
collectionOperations: [
'get',
'post' => ['security' => "is_granted('ROLE_MASTER_MANAGER')"],
],
itemOperations: [
'get',
'put' => ['security' => "is_granted('PROJECT_EDIT', object)"],
'patch' => ['security' => "is_granted('PROJECT_EDIT', object)"],
'delete' => [
'security' => "is_granted('PROJECT_EDIT', object)",
'method' => 'DELETE',
],
'remove-manager' => [
'security' => "is_granted('PROJECT_EDIT', object)",
'method' => 'DELETE',
'path' => '/projects/{slug}/managers/{id}',
'controller' => RemoveManagerFromProjectController::class,
'openapi_context' => [
'summary' => 'Remove manager from project',
'description' => 'Remove manager from project',
'parameters' => [['in' => 'path', 'name' => 'slug', 'required' => true], ['in' => 'path', 'name' => 'id', 'required' => true]],
],
],
'remove-developer' => [
'security' => "is_granted('PROJECT_EDIT', object)",
'method' => 'DELETE',
'path' => '/projects/{slug}/developers/{id}',
'controller' => RemoveDeveloperFromProjectController::class,
'openapi_context' => [
'summary' => 'Remove developer from project', 'description' => 'Remove developer from project',
'parameters' => [['in' => 'path', 'name' => 'slug', 'required' => true], ['in' => 'path', 'name' => 'id', 'required' => true]],
],
],
Project::PATCH_TE_OPERATION => [
'method' => 'PATCH',
'path' => 'projects/{slug}/time-entries',
'input' => TimeEntryFixInput::class,
'output' => TimeEntryFixOutput::class,
'security' => "is_granted('ROLE_PROJECT_FIXER')",
'openapi_context' => ['summary' => 'Set fix $ approved for time-entries'],
],
],
attributes: [
'order' => ['title' => 'ASC'],
],
input: ProjectInput::class,
output: ProjectOutput::class,
)]
#[ApiFilter(SearchFilter::class, properties: ['managers.id' => 'exact'])]
#[ApiFilter(SearchFilter::class, properties: [
'title' => 'i' . SearchFilter::STRATEGY_PARTIAL,
'description' => 'i' . SearchFilter::STRATEGY_PARTIAL,
])]
#[ApiFilter(BooleanFilter::class, properties: ['archived', 'isCommercial'])]
#[ORM\Entity(repositoryClass: 'App\Repository\ProjectRepository')]
#[ORM\Table]
#[ORM\Index(name: 'idx_project_archived', columns: ['archived'])]
#[ORM\Index(name: 'idx_project_is_commercial', columns: ['is_commercial'])]
#[UniqueEntity(fields: ['slug'], message: 'Это Название проекта уже используется')]
class Project implements UuidKeyInterface, SluggableInterface, \Stringable
{
use SlugAsIdTrait;
use TimestampableEntity;
public const READ_GROUP = 'Project:read';
public const PATCH_TE_OPERATION = 'patch-te';
public const TITLE_DESCRIPTION = 'Название проекта';
#[Assert\NotBlank]
#[Groups([self::READ_GROUP])]
#[Assert\Length(max: 255)]
#[ORM\Column(nullable: true)]
private ?string $title = null;
#[Groups([self::READ_GROUP])]
#[ApiProperty(description: self::TITLE_DESCRIPTION)]
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description = null;
#[Assert\Valid]
#[Groups([self::READ_GROUP])]
#[ORM\ManyToOne(targetEntity: Contractor::class, inversedBy: 'projects', cascade: ['persist'])]
#[ORM\JoinColumn(nullable: false)]
private ?Contractor $contractor = null;
/**
* @var Collection<array-key, Manager>
*/
#[ApiSubresource]
#[ORM\ManyToMany(targetEntity: 'App\Entity\Project\Manager', mappedBy: 'projects')]
private Collection $managers;
/**
* @var Collection<array-key, Developer>
*/
#[ApiSubresource]
#[ORM\ManyToMany(targetEntity: 'App\Entity\Project\Developer', mappedBy: 'projects')]
private Collection $developers;
/**
* @var Collection<array-key, Stage>
*/
#[Groups([self::READ_GROUP])]
#[ApiSubresource]
#[ORM\OneToMany(targetEntity: 'App\Entity\Project\Stage', mappedBy: 'project')]
private Collection $stages;
#[Groups([self::READ_GROUP])]
#[ORM\ManyToOne(targetEntity: 'App\Entity\Project\Stage')]
private ?Stage $currentStage = null;
/**
* @var Collection<array-key, TimeEntry>
*/
#[ApiSubresource]
#[ORM\OneToMany(targetEntity: 'App\Entity\TimeControl\TimeEntry', mappedBy: 'project')]
private Collection $timeEntries;
#[ORM\Column(type: 'boolean', options: ['default' => false])]
private bool $archived = false;
/**
* @var Collection<array-key, DeveloperRate>
*/
#[ORM\OneToMany(targetEntity: DeveloperRate::class, mappedBy: 'project', cascade: ['persist', 'remove'])]
private Collection $rates;
/**
* @var Collection<array-key, Plan>
*/
#[ORM\OneToMany(targetEntity: Plan::class, mappedBy: 'project')]
private Collection $plans;
/**
* @var Collection<array-key, Month>
*/
#[ORM\OneToMany(targetEntity: Month::class, mappedBy: 'project')]
private Collection $months;
#[ORM\Column(type: 'date', nullable: true, options: ['default' => null])]
private ?\DateTimeInterface $dateSigning = null;
#[ORM\Column(type: 'datetime', nullable: true)]
private ?\DateTimeInterface $deletedAt = null;
/**
* @var Collection<int, Task>
*/
#[ORM\OneToMany(targetEntity: Task::class, mappedBy: 'project')]
private Collection $tasks;
#[ORM\Column(type: 'boolean', options: ['default' => true])]
private ?bool $isCommercial = true;
#[ORM\ManyToOne(targetEntity: Unit::class, inversedBy: 'projects')]
private ?Unit $unit = null;
/**
* Project constructor.
*/
public function __construct()
{
$this->managers = new ArrayCollection();
$this->developers = new ArrayCollection();
$this->stages = new ArrayCollection();
$this->timeEntries = new ArrayCollection();
$this->rates = new ArrayCollection();
$this->plans = new ArrayCollection();
$this->months = new ArrayCollection();
$this->tasks = new ArrayCollection();
}
public function __toString(): string
{
return $this->title ?? __CLASS__;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getContractor(): ?Contractor
{
return $this->contractor;
}
public function setContractor(?Contractor $contractor): self
{
$this->contractor = $contractor;
return $this;
}
/**
* @return Collection|Manager[]
*
* @psalm-return Collection
*/
public function getManagers(): Collection
{
return $this->managers;
}
public function addManager(Manager $manager): self
{
if (!$this->managers->contains($manager)) {
$this->managers[] = $manager;
$manager->addProject($this);
}
return $this;
}
public function removeManager(Manager $manager): self
{
if ($this->managers->contains($manager)) {
$this->managers->removeElement($manager);
$manager->removeProject($this);
}
return $this;
}
public function getManagerNames(): array
{
$data = [];
foreach ($this->managers as $manager) {
if (!$manager instanceof Manager || ($user = $manager->getUser()) === null) {
continue;
}
if (!$user instanceof User || ($profile = $user->getProfile()) === null) {
continue;
}
$data[] = $profile->getPerson()->getFullName();
}
return $data;
}
/**
* @return Collection<int, Developer>
*/
public function getDevelopers(): Collection
{
return $this->developers;
}
public function addDeveloper(Developer $developer): self
{
if (!$this->developers->contains($developer)) {
$this->developers[] = $developer;
$developer->addProject($this);
}
return $this;
}
public function removeDeveloper(Developer $developer): self
{
if ($this->developers->contains($developer)) {
$this->developers->removeElement($developer);
$developer->removeProject($this);
}
return $this;
}
/**
* @return Collection|Stage[]
*
* @psalm-return Collection
*/
public function getStages(): Collection
{
return $this->stages;
}
public function addStage(Stage $stage): self
{
if (!$this->stages->contains($stage)) {
$this->stages[] = $stage;
$stage->setProject($this);
}
return $this;
}
public function removeStage(Stage $stage): self
{
if ($this->stages->contains($stage)) {
$this->stages->removeElement($stage);
// set the owning side to null (unless already changed)
if ($stage->getProject() === $this) {
$stage->setProject(null);
}
}
return $this;
}
public function getCurrentStage(): ?Stage
{
return $this->currentStage;
}
public function setCurrentStage(?Stage $currentStage): self
{
$this->currentStage = $currentStage;
return $this;
}
/**
* @psalm-return Collection<int, TimeEntry>
*/
public function getTimeEntries(): Collection
{
return $this->timeEntries;
}
public function addTimeEntry(TimeEntry $timeEntry): self
{
if (!$this->timeEntries->contains($timeEntry)) {
$this->timeEntries[] = $timeEntry;
$timeEntry->setProject($this);
}
return $this;
}
public function removeTimeEntry(TimeEntry $timeEntry): self
{
if ($this->timeEntries->contains($timeEntry)) {
$this->timeEntries->removeElement($timeEntry);
// set the owning side to null (unless already changed)
if ($timeEntry->getProject() === $this) {
$timeEntry->setProject(null);
}
}
return $this;
}
public function getArchived(): ?bool
{
return $this->archived;
}
public function setArchived(bool $archived): self
{
$this->archived = $archived;
return $this;
}
public function getRate(Developer $developer): ?DeveloperRate
{
$result = $this->rates->map(fn (DeveloperRate $rate) => $rate->getDeveloper() === $developer);
if (($resultRate = $result->first()) instanceof DeveloperRate) {
return $resultRate;
}
return null;
}
/**
* @return Collection<array-key, DeveloperRate>|DeveloperRate[]
*
* @psalm-return Collection<array-key, DeveloperRate>
*/
public function getRates(): Collection
{
return $this->rates;
}
public function addRate(DeveloperRate $rate): self
{
if (!$this->rates->contains($rate)) {
$this->rates[] = $rate;
$rate->setProject($this);
}
return $this;
}
public function removeRate(DeveloperRate $rate): self
{
if ($this->rates->removeElement($rate)) {
if ($rate->getProject() === $this) {
$rate->setProject(null);
}
}
return $this;
}
/**
* @return Collection|Plan[]
*
* @psalm-return Collection<array-key, Plan>
*/
public function getPlans(): Collection
{
return $this->plans;
}
public function addPlan(Plan $plan): self
{
if (!$this->plans->contains($plan)) {
$this->plans[] = $plan;
$plan->setProject($this);
}
return $this;
}
public function removePlan(Plan $plan): self
{
if ($this->plans->removeElement($plan)) {
// set the owning side to null (unless already changed)
if ($plan->getProject() === $this) {
$plan->setProject(null);
}
}
return $this;
}
/**
* @return Collection<array-key, Month>|Month[]
*
* @psalm-return Collection<array-key, Month>
*/
public function getMonths(): Collection
{
return $this->months;
}
public function addMonth(Month $month): self
{
if (!$this->months->contains($month)) {
$this->months[] = $month;
$month->setProject($this);
}
return $this;
}
public function removeMonth(Month $month): self
{
if ($this->months->removeElement($month)) {
// set the owning side to null (unless already changed)
if ($month->getProject() === $this) {
$month->setProject(null);
}
}
return $this;
}
public function getDateSigning(): ?\DateTimeInterface
{
return $this->dateSigning;
}
public function setDateSigning(?\DateTimeInterface $dateSigning): self
{
$this->dateSigning = $dateSigning;
return $this;
}
public function getTasks(): Collection
{
return $this->tasks;
}
public function addTask(Task $task): self
{
if (!$this->tasks->contains($task)) {
$this->tasks[] = $task;
$task->setProject($this);
}
return $this;
}
public function removeTask(Task $task): self
{
if ($this->tasks->contains($task)) {
$this->tasks->removeElement($task);
$task->setProject(null);
}
return $this;
}
public function getDeletedAt(): ?\DateTimeInterface
{
return $this->deletedAt;
}
public function setDeletedAt(?\DateTimeInterface $deletedAt): void
{
$this->deletedAt = $deletedAt;
}
public function getIsCommercial(): ?bool
{
return $this->isCommercial;
}
public function setIsCommercial(?bool $isCommercial): self
{
$this->isCommercial = $isCommercial;
return $this;
}
public function getUnit(): ?Unit
{
return $this->unit;
}
public function setUnit(?Unit $unit): self
{
$this->unit = $unit;
return $this;
}
}