move goal progress calculation out of the resources

This commit is contained in:
Gregor Vostrak
2026-10-06 18:39:35 +02:00
parent f9519d0d5a
commit 3a7a354551
7 changed files with 149 additions and 26 deletions

View File

@@ -14,6 +14,7 @@ use App\Http\Resources\V1\Goal\GoalResource;
use App\Models\Goal;
use App\Models\Organization;
use App\Service\BillingContract;
use App\Service\GoalProgressService;
use App\Service\GoalsContract;
use App\Service\TimezoneService;
use Illuminate\Auth\Access\AuthorizationException;
@@ -73,7 +74,7 @@ class GoalController extends Controller
*
* @operationId getGoals
*/
public function index(Organization $organization, GoalIndexRequest $request, GoalsContract $access): GoalCollection
public function index(Organization $organization, GoalIndexRequest $request, GoalsContract $access, GoalProgressService $progressService): GoalCollection
{
// An organization goal is visible to the member it is for, who does not hold the :organization-type permission.
// Either permission is enough, the access contract decides which goals come back.
@@ -96,8 +97,9 @@ class GoalController extends Controller
}
$goals = $query->paginate(config('app.pagination_per_page_default'));
$progressByGoalId = $progressService->getCurrentProgressForGoals($goals->getCollection(), Carbon::now());
return new GoalCollection($goals);
return new GoalCollection($goals, $progressByGoalId);
}
/**
@@ -107,7 +109,7 @@ class GoalController extends Controller
*
* @operationId getGoal
*/
public function show(Organization $organization, Goal $goal, GoalsContract $access): GoalResource
public function show(Organization $organization, Goal $goal, GoalsContract $access, GoalProgressService $progressService): GoalResource
{
// Either permission is enough, see index
$this->checkAnyPermission($organization, ['goals:view:own', 'goals:view:organization-type']);
@@ -118,7 +120,7 @@ class GoalController extends Controller
}
$goal->load('member.user');
return new GoalResource($goal);
return new GoalResource($goal, $progressService->getCurrentProgress($goal, Carbon::now()));
}
/**
@@ -128,7 +130,7 @@ class GoalController extends Controller
*
* @operationId createGoal
*/
public function store(Organization $organization, GoalStoreRequest $request, GoalsContract $access): JsonResponse
public function store(Organization $organization, GoalStoreRequest $request, GoalsContract $access, GoalProgressService $progressService): JsonResponse
{
if ($request->getType() === GoalType::Personal) {
$this->checkPermission($organization, 'goals:create:own');
@@ -159,7 +161,7 @@ class GoalController extends Controller
$goal->save();
$goal->load('member.user');
return (new GoalResource($goal))
return (new GoalResource($goal, $progressService->getCurrentProgress($goal, Carbon::now())))
->response()
->setStatusCode(201);
}
@@ -173,7 +175,7 @@ class GoalController extends Controller
*
* @operationId updateGoal
*/
public function update(Organization $organization, Goal $goal, GoalUpdateRequest $request, GoalsContract $access): GoalResource
public function update(Organization $organization, Goal $goal, GoalUpdateRequest $request, GoalsContract $access, GoalProgressService $progressService): GoalResource
{
if ($goal->type === GoalType::Personal) {
$this->checkPermission($organization, 'goals:update:own', $goal);
@@ -212,7 +214,7 @@ class GoalController extends Controller
$goal->save();
$goal->load('member.user');
return new GoalResource($goal);
return new GoalResource($goal, $progressService->getCurrentProgress($goal, Carbon::now()));
}
/**

View File

@@ -5,14 +5,42 @@ declare(strict_types=1);
namespace App\Http\Resources\V1\Goal;
use App\Http\Resources\PaginatedResourceCollection;
use App\Models\Goal;
use App\Service\Dto\GoalProgressDto;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class GoalCollection extends ResourceCollection implements PaginatedResourceCollection
{
/**
* The resource that this resource collects.
*
* @var string
* @var array<string, GoalProgressDto>
*/
public $collects = GoalResource::class;
private array $progressByGoalId;
/**
* @param array<string, GoalProgressDto> $progressByGoalId
*/
public function __construct($resource, array $progressByGoalId)
{
parent::__construct($resource);
$this->progressByGoalId = $progressByGoalId;
}
protected function collects(): ?string
{
return null;
}
/**
* Transform the resource collection into an array.
*
* @return array<array<string, string|bool|int|null|array<string, string|bool|int|null|array<int, string>>>>
*/
public function toArray(Request $request): array
{
return $this->collection->map(function (Goal $goal) use ($request): array {
return (new GoalResource($goal, $this->progressByGoalId[$goal->getKey()]))
->toArray($request);
})->all();
}
}

View File

@@ -6,15 +6,23 @@ namespace App\Http\Resources\V1\Goal;
use App\Http\Resources\V1\BaseResource;
use App\Models\Goal;
use App\Service\GoalProgressService;
use App\Service\Dto\GoalProgressDto;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
/**
* @property Goal $resource
*/
class GoalResource extends BaseResource
{
private GoalProgressDto $progress;
public function __construct(Goal $resource, GoalProgressDto $progress)
{
parent::__construct($resource);
$this->progress = $progress;
}
/**
* Transform the resource into an array.
*
@@ -22,11 +30,6 @@ class GoalResource extends BaseResource
*/
public function toArray(Request $request): array
{
$progressService = app(GoalProgressService::class);
$now = Carbon::now();
[$periodStart, $periodEnd] = $progressService->getPeriodBounds($this->resource, $now);
$trackedSeconds = $progressService->getProgress($this->resource, $periodStart, $periodEnd);
return [
/** @var string $id ID of the goal */
'id' => $this->resource->id,
@@ -70,13 +73,13 @@ class GoalResource extends BaseResource
],
'progress' => [
/** @var string $period_start Start of the current period (inclusive) */
'period_start' => $this->formatDateTime($periodStart),
'period_start' => $this->formatDateTime($this->progress->periodStart),
/** @var string $period_end End of the current period (exclusive) */
'period_end' => $this->formatDateTime($periodEnd),
'period_end' => $this->formatDateTime($this->progress->periodEnd),
/** @var int $tracked_seconds Seconds tracked in the current period that match the filters, incl. the running time entry */
'tracked_seconds' => $trackedSeconds,
'tracked_seconds' => $this->progress->trackedSeconds,
/** @var string $status Status in the current period (in_progress, achieved, on_track, exceeded) */
'status' => $progressService->getStatus($this->resource, $trackedSeconds)->value,
'status' => $this->progress->status->value,
],
/** @var string $created_at Date when the goal was created */
'created_at' => $this->formatDateTime($this->resource->created_at),