")] #[Serializer\Accessor(getter: 'getOrderDate')] private ?\DateTime $orderDate = null; /** * Project start date (times before this date cannot be recorded) * * Attention: Accessor MUST be used, otherwise date will be serialized in UTC. */ #[ORM\Column(name: 'start', type: Types::DATETIME_MUTABLE, nullable: true)] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Serializer\Type(name: "DateTime<'Y-m-d'>")] #[Serializer\Accessor(getter: 'getStart')] private ?\DateTime $start = null; /** * Project end time (times after this date cannot be recorded) * * Attention: Accessor MUST be used, otherwise date will be serialized in UTC. */ #[ORM\Column(name: 'end', type: Types::DATETIME_MUTABLE, nullable: true)] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Serializer\Type(name: "DateTime<'Y-m-d'>")] #[Serializer\Accessor(getter: 'getEnd')] private ?\DateTime $end = null; #[ORM\Column(name: 'timezone', type: Types::STRING, length: 64, nullable: true)] private ?string $timezone = null; private bool $localized = false; #[ORM\Column(name: 'comment', type: Types::TEXT, nullable: true)] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Exporter\Expose(label: 'comment')] private ?string $comment = null; /** * If the project is not visible, times cannot be recorded */ #[ORM\Column(name: 'visible', type: Types::BOOLEAN, nullable: false)] #[Assert\NotNull] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Exporter\Expose(label: 'visible', type: 'boolean')] private bool $visible = true; #[ORM\Column(name: 'billable', type: Types::BOOLEAN, nullable: false, options: ['default' => true])] #[Assert\NotNull] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Exporter\Expose(label: 'billable', type: 'boolean')] private bool $billable = true; /** * Meta fields registered with the project * * @var Collection */ #[ORM\OneToMany(mappedBy: 'project', targetEntity: ProjectMeta::class, cascade: ['persist'])] #[Serializer\Expose] #[Serializer\Groups(['Project'])] #[Serializer\Type(name: 'array')] #[Serializer\SerializedName('metaFields')] #[Serializer\Accessor(getter: 'getVisibleMetaFields')] private Collection $meta; /** * Teams with access to the project * * @var Collection */ #[ORM\JoinTable(name: 'kimai2_projects_teams')] #[ORM\JoinColumn(name: 'project_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[ORM\InverseJoinColumn(name: 'team_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[ORM\ManyToMany(targetEntity: Team::class, inversedBy: 'projects', cascade: ['persist'])] #[Serializer\Expose] #[Serializer\Groups(['Project'])] #[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))] private Collection $teams; #[ORM\Column(name: 'invoice_text', type: Types::TEXT, nullable: true)] private ?string $invoiceText = null; /** * Whether this project allows booking of global activities */ #[ORM\Column(name: 'global_activities', type: Types::BOOLEAN, nullable: false, options: ['default' => true])] #[Assert\NotNull] #[Serializer\Expose] #[Serializer\Groups(['Default'])] private bool $globalActivities = true; #[ORM\Column(name: 'number', type: Types::STRING, length: 10, nullable: true)] #[Assert\Length(max: 10)] #[Serializer\Expose] #[Serializer\Groups(['Default'])] #[Exporter\Expose(label: 'project_number')] private ?string $number = null; public function __construct() { $this->meta = new ArrayCollection(); $this->teams = new ArrayCollection(); $this->setCreatedAt(new \DateTimeImmutable('now', new \DateTimeZone('UTC'))); } public function getId(): ?int { return $this->id; } public function isNew(): bool { return $this->id === null; } public function getCustomer(): ?Customer { return $this->customer; } public function setCustomer(?Customer $customer): Project { $this->customer = $customer; return $this; } public function setName(string $name): Project { $this->name = $name; return $this; } public function getName(): ?string { return $this->name; } public function setComment(?string $comment): Project { $this->comment = $comment; return $this; } public function getComment(): ?string { return $this->comment; } public function setVisible(bool $visible): Project { $this->visible = $visible; return $this; } public function isVisible(): bool { return $this->visible; } public function setBillable(bool $billable): void { $this->billable = $billable; } public function isBillable(): bool { return $this->billable; } public function getOrderNumber(): ?string { return $this->orderNumber; } public function setOrderNumber(?string $orderNumber): Project { $this->orderNumber = $orderNumber; return $this; } /** * Make sure begin and end date have the correct timezone. * This will be called once for each item after being loaded from the database. */ protected function localizeDates(): void { if ($this->localized) { return; } if (null === $this->timezone) { $this->timezone = date_default_timezone_get(); } $timezone = new \DateTimeZone($this->timezone); if (null !== $this->orderDate) { $this->orderDate->setTimezone($timezone); } if (null !== $this->start) { $this->start->setTimezone($timezone); } if (null !== $this->end) { $this->end->setTimezone($timezone); } $this->localized = true; } #[Exporter\Expose(name: 'orderDate', label: 'orderDate', type: 'date')] public function getOrderDate(): ?\DateTime { $this->localizeDates(); return $this->orderDate; } public function setOrderDate(?\DateTime $orderDate): Project { $this->orderDate = $orderDate; if (null !== $orderDate) { $this->timezone = $orderDate->getTimezone()->getName(); } return $this; } #[Exporter\Expose(name: 'start', label: 'project_start', type: 'date')] public function getStart(): ?\DateTime { $this->localizeDates(); return $this->start; } public function setStart(?\DateTime $start): Project { $this->start = $start; if (null !== $start) { $this->timezone = $start->getTimezone()->getName(); } return $this; } #[Exporter\Expose(name: 'end', label: 'project_end', type: 'date')] public function getEnd(): ?\DateTime { $this->localizeDates(); return $this->end; } public function setEnd(?\DateTime $end): Project { $this->end = $end; if (null !== $end) { $this->timezone = $end->getTimezone()->getName(); } return $this; } public function isGlobalActivities(): bool { return $this->globalActivities; } public function setGlobalActivities(bool $globalActivities): void { $this->globalActivities = $globalActivities; } /** * @return Collection|MetaTableTypeInterface[] */ public function getMetaFields(): Collection { return $this->meta; } /** * @return MetaTableTypeInterface[] */ public function getVisibleMetaFields(): array { $all = []; foreach ($this->meta as $meta) { if ($meta->isVisible()) { $all[] = $meta; } } return $all; } public function getMetaField(string $name): ?MetaTableTypeInterface { foreach ($this->meta as $field) { if (strtolower($field->getName()) === strtolower($name)) { return $field; } } return null; } public function setMetaField(MetaTableTypeInterface $meta): EntityWithMetaFields { if (null === ($current = $this->getMetaField($meta->getName()))) { $meta->setEntity($this); $this->meta->add($meta); return $this; } $current->merge($meta); return $this; } public function addTeam(Team $team): void { if ($this->teams->contains($team)) { return; } $this->teams->add($team); $team->addProject($this); } public function removeTeam(Team $team): void { if (!$this->teams->contains($team)) { return; } $this->teams->removeElement($team); $team->removeProject($this); } /** * @return Collection */ public function getTeams(): Collection { return $this->teams; } public function isVisibleAtDate(\DateTime $dateTime): bool { if (!$this->isVisible()) { return false; } if ($this->getCustomer() !== null && !$this->getCustomer()->isVisible()) { return false; } if ($this->getStart() !== null && $dateTime < $this->getStart()) { return false; } if ($this->getEnd() !== null && $dateTime > $this->getEnd()) { return false; } return true; } public function getInvoiceText(): ?string { return $this->invoiceText; } public function setInvoiceText(?string $invoiceText): void { $this->invoiceText = $invoiceText; } public function setNumber(?string $number): void { $this->number = $number; } public function getNumber(): ?string { return $this->number; } public function __toString(): string { return $this->getName(); } public function __clone() { if ($this->id !== null) { $this->id = null; } $this->setCreatedAt(new \DateTimeImmutable('now', new \DateTimeZone('UTC'))); $currentTeams = $this->teams; $this->teams = new ArrayCollection(); /** @var Team $team */ foreach ($currentTeams as $team) { $this->addTeam($team); } $this->number = null; $currentMeta = $this->meta; $this->meta = new ArrayCollection(); /** @var ProjectMeta $meta */ foreach ($currentMeta as $meta) { $newMeta = clone $meta; $newMeta->setEntity($this); $this->setMetaField($newMeta); } } }