mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-16 17:53:18 +01:00
Add goals feature for personal goals with filters
This commit is contained in:
169
app/Service/Dto/GoalFiltersDto.php
Normal file
169
app/Service/Dto/GoalFiltersDto.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Dto;
|
||||
|
||||
use App\Enums\TagMatchType;
|
||||
use App\Enums\TimeEntryType;
|
||||
use App\Service\TimeEntryFilter;
|
||||
use Illuminate\Contracts\Database\Eloquent\Castable;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Persisted filter set of a goal. Defines which time entries count towards the goal.
|
||||
* A null value means "no restriction" for that dimension. The ID setters store an empty array as null,
|
||||
* so "no restriction" has a single representation.
|
||||
*/
|
||||
class GoalFiltersDto implements Castable
|
||||
{
|
||||
/**
|
||||
* Members whose time entries count. Only used by goals that count every member, null means every member.
|
||||
*
|
||||
* @var Collection<int, string>|null
|
||||
*/
|
||||
public ?Collection $memberIds = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, string>|null
|
||||
*/
|
||||
public ?Collection $projectIds = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, string>|null
|
||||
*/
|
||||
public ?Collection $taskIds = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, string>|null
|
||||
*/
|
||||
public ?Collection $tagIds = null;
|
||||
|
||||
public ?TagMatchType $tagMatchType = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, string>|null
|
||||
*/
|
||||
public ?Collection $clientIds = null;
|
||||
|
||||
public ?bool $billable = null;
|
||||
|
||||
public ?TimeEntryType $timeEntryType = null;
|
||||
|
||||
/**
|
||||
* Get the caster class to use when casting from / to this cast target.
|
||||
*
|
||||
* @param array<string, mixed> $arguments
|
||||
* @return CastsAttributes<GoalFiltersDto, GoalFiltersDto>
|
||||
*/
|
||||
public static function castUsing(array $arguments): CastsAttributes
|
||||
{
|
||||
return new class implements CastsAttributes
|
||||
{
|
||||
private const array REQUIRED_PROPERTIES = [
|
||||
'memberIds',
|
||||
'projectIds',
|
||||
'taskIds',
|
||||
'tagIds',
|
||||
'tagMatchType',
|
||||
'clientIds',
|
||||
'billable',
|
||||
'timeEntryType',
|
||||
];
|
||||
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): GoalFiltersDto
|
||||
{
|
||||
if (! is_string($value)) {
|
||||
throw new \InvalidArgumentException('The given value is not a string');
|
||||
}
|
||||
$data = json_decode($value, false);
|
||||
if (! is_object($data)) {
|
||||
throw new \InvalidArgumentException('The given value is not a JSON object string');
|
||||
}
|
||||
foreach (self::REQUIRED_PROPERTIES as $property) {
|
||||
if (! property_exists($data, $property)) {
|
||||
throw new \InvalidArgumentException('The given JSON string does not contain the required property "'.$property.'"');
|
||||
}
|
||||
}
|
||||
$dto = new GoalFiltersDto;
|
||||
$dto->setMemberIds(isset($data->memberIds) ? (array) $data->memberIds : null);
|
||||
$dto->setProjectIds(isset($data->projectIds) ? (array) $data->projectIds : null);
|
||||
$dto->setTaskIds(isset($data->taskIds) ? (array) $data->taskIds : null);
|
||||
$dto->setTagIds(isset($data->tagIds) ? (array) $data->tagIds : null);
|
||||
$dto->tagMatchType = isset($data->tagMatchType) ? TagMatchType::from($data->tagMatchType) : null;
|
||||
$dto->setClientIds(isset($data->clientIds) ? (array) $data->clientIds : null);
|
||||
$dto->billable = isset($data->billable) ? (bool) $data->billable : null;
|
||||
$dto->timeEntryType = isset($data->timeEntryType) ? TimeEntryType::from($data->timeEntryType) : null;
|
||||
|
||||
return $dto;
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): string
|
||||
{
|
||||
if (! ($value instanceof GoalFiltersDto)) {
|
||||
throw new \InvalidArgumentException('The given value is not an instance of GoalFiltersDto');
|
||||
}
|
||||
|
||||
$data = (object) [
|
||||
'memberIds' => $value->memberIds?->toArray(),
|
||||
'projectIds' => $value->projectIds?->toArray(),
|
||||
'taskIds' => $value->taskIds?->toArray(),
|
||||
'tagIds' => $value->tagIds?->toArray(),
|
||||
'tagMatchType' => $value->tagMatchType?->value,
|
||||
'clientIds' => $value->clientIds?->toArray(),
|
||||
'billable' => $value->billable,
|
||||
'timeEntryType' => $value->timeEntryType?->value,
|
||||
];
|
||||
|
||||
$jsonString = json_encode($data);
|
||||
if ($jsonString === false) {
|
||||
throw new \InvalidArgumentException('Could not encode the given data to a JSON string');
|
||||
}
|
||||
|
||||
return $jsonString;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $memberIds
|
||||
*/
|
||||
public function setMemberIds(?array $memberIds): void
|
||||
{
|
||||
$this->memberIds = $memberIds !== null && count($memberIds) > 0 ? TimeEntryFilter::idArrayToCollection($memberIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $projectIds
|
||||
*/
|
||||
public function setProjectIds(?array $projectIds): void
|
||||
{
|
||||
$this->projectIds = $projectIds !== null && count($projectIds) > 0 ? TimeEntryFilter::idArrayToCollection($projectIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $taskIds
|
||||
*/
|
||||
public function setTaskIds(?array $taskIds): void
|
||||
{
|
||||
$this->taskIds = $taskIds !== null && count($taskIds) > 0 ? TimeEntryFilter::idArrayToCollection($taskIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $tagIds
|
||||
*/
|
||||
public function setTagIds(?array $tagIds): void
|
||||
{
|
||||
$this->tagIds = $tagIds !== null && count($tagIds) > 0 ? TimeEntryFilter::idArrayToCollection($tagIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $clientIds
|
||||
*/
|
||||
public function setClientIds(?array $clientIds): void
|
||||
{
|
||||
$this->clientIds = $clientIds !== null && count($clientIds) > 0 ? TimeEntryFilter::idArrayToCollection($clientIds) : null;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class ReportPropertiesDto implements Castable
|
||||
@@ -117,13 +116,13 @@ class ReportPropertiesDto implements Castable
|
||||
$dto->end = $data->end !== null ? Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $data->end) : null;
|
||||
$dto->start = $data->start !== null ? Carbon::createFromFormat('Y-m-d\TH:i:s\Z', $data->start) : null;
|
||||
$dto->active = $data->active;
|
||||
$dto->memberIds = $data->memberIds !== null ? ReportPropertiesDto::idArrayToCollection($data->memberIds) : null;
|
||||
$dto->memberIds = $data->memberIds !== null ? TimeEntryFilter::idArrayToCollection($data->memberIds) : null;
|
||||
$dto->billable = $data->billable;
|
||||
$dto->clientIds = $data->clientIds !== null ? ReportPropertiesDto::idArrayToCollection($data->clientIds) : null;
|
||||
$dto->projectIds = $data->projectIds !== null ? ReportPropertiesDto::idArrayToCollection($data->projectIds) : null;
|
||||
$dto->tagIds = $data->tagIds !== null ? ReportPropertiesDto::idArrayToCollection($data->tagIds) : null;
|
||||
$dto->clientIds = $data->clientIds !== null ? TimeEntryFilter::idArrayToCollection($data->clientIds) : null;
|
||||
$dto->projectIds = $data->projectIds !== null ? TimeEntryFilter::idArrayToCollection($data->projectIds) : null;
|
||||
$dto->tagIds = $data->tagIds !== null ? TimeEntryFilter::idArrayToCollection($data->tagIds) : null;
|
||||
$dto->tagMatchType = isset($data->tagMatchType) ? TagMatchType::from($data->tagMatchType) : null;
|
||||
$dto->taskIds = $data->taskIds ? ReportPropertiesDto::idArrayToCollection($data->taskIds) : null;
|
||||
$dto->taskIds = $data->taskIds ? TimeEntryFilter::idArrayToCollection($data->taskIds) : null;
|
||||
$dto->group = TimeEntryAggregationType::from($data->group);
|
||||
$dto->subGroup = TimeEntryAggregationType::from($data->subGroup);
|
||||
$dto->historyGroup = TimeEntryAggregationTypeInterval::from($data->historyGroup);
|
||||
@@ -180,32 +179,12 @@ class ReportPropertiesDto implements Castable
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $ids
|
||||
* @return Collection<int, string>
|
||||
*/
|
||||
public static function idArrayToCollection(array $ids): Collection
|
||||
{
|
||||
$collection = new Collection;
|
||||
foreach ($ids as $id) {
|
||||
if (! is_string($id)) {
|
||||
throw new InvalidArgumentException('The given ID is not a string');
|
||||
}
|
||||
if ($id !== TimeEntryFilter::NONE_VALUE && ! Str::isUuid($id)) {
|
||||
throw new InvalidArgumentException('The given ID is not a valid UUID');
|
||||
}
|
||||
$collection->push($id);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed>|null $memberIds
|
||||
*/
|
||||
public function setMemberIds(?array $memberIds): void
|
||||
{
|
||||
$this->memberIds = $memberIds !== null ? ReportPropertiesDto::idArrayToCollection($memberIds) : null;
|
||||
$this->memberIds = $memberIds !== null ? TimeEntryFilter::idArrayToCollection($memberIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,7 +192,7 @@ class ReportPropertiesDto implements Castable
|
||||
*/
|
||||
public function setClientIds(?array $clientIds): void
|
||||
{
|
||||
$this->clientIds = $clientIds !== null ? ReportPropertiesDto::idArrayToCollection($clientIds) : null;
|
||||
$this->clientIds = $clientIds !== null ? TimeEntryFilter::idArrayToCollection($clientIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,7 +200,7 @@ class ReportPropertiesDto implements Castable
|
||||
*/
|
||||
public function setProjectIds(?array $projectIds): void
|
||||
{
|
||||
$this->projectIds = $projectIds !== null ? ReportPropertiesDto::idArrayToCollection($projectIds) : null;
|
||||
$this->projectIds = $projectIds !== null ? TimeEntryFilter::idArrayToCollection($projectIds) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +208,7 @@ class ReportPropertiesDto implements Castable
|
||||
*/
|
||||
public function setTagIds(?array $tagIds): void
|
||||
{
|
||||
$this->tagIds = $tagIds !== null ? ReportPropertiesDto::idArrayToCollection($tagIds) : null;
|
||||
$this->tagIds = $tagIds !== null ? TimeEntryFilter::idArrayToCollection($tagIds) : null;
|
||||
}
|
||||
|
||||
public function setTagMatchType(?TagMatchType $tagMatchType): void
|
||||
@@ -242,6 +221,6 @@ class ReportPropertiesDto implements Castable
|
||||
*/
|
||||
public function setTaskIds(?array $taskIds): void
|
||||
{
|
||||
$this->taskIds = $taskIds !== null ? ReportPropertiesDto::idArrayToCollection($taskIds) : null;
|
||||
$this->taskIds = $taskIds !== null ? TimeEntryFilter::idArrayToCollection($taskIds) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user