feat(slepok): Task 2.11 (backend slice) — ProjectResource serializes applies_from

ProjectResource теперь включает поле `applies_from` (ISO8601 строка | null) в
JSON-ответе. Установлен ProjectService::update() для slepok-sensitive правок
(Task 2.8 dynamic attribute).

UI Vue/composables/Vitest часть откладывается на отдельную сессию — это
backend-only commit для бэкенд-инструмента UI-сообщения.

Spec §4.2.5.

Plan: docs/superpowers/plans/2026-05-26-slepok-routing-protection.md §Task 2.11

Tests: tests/Feature/Http/Resources/ProjectResourceAppliesFromTest.php — 2/2 PASS.
This commit is contained in:
Дмитрий
2026-05-28 07:02:49 +03:00
parent 6d6fa10d91
commit dd5954d8a5
2 changed files with 37 additions and 0 deletions
@@ -35,6 +35,10 @@ class ProjectResource extends JsonResource
$request->routeIs('projects.show'),
fn () => $this->getSupplierLinks(),
),
// Task 2.11 (Spec §4.2.5): dynamic attribute, не БД-поле. Установлен
// ProjectService::update() для slepok-sensitive правок. UI показывает
// «изменения вступят в силу с DD.MM HH:MM МСК».
'applies_from' => $this->applies_from?->toIso8601String(),
];
}
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
use App\Http\Resources\ProjectResource;
use App\Models\Project;
use App\Models\Tenant;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Testing\DatabaseTransactions;
uses(DatabaseTransactions::class);
it('ProjectResource includes applies_from as ISO8601 when set', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
$project->applies_from = CarbonImmutable::parse('2026-05-29 21:00:00', 'Europe/Moscow');
$resource = (new ProjectResource($project))->toArray(request());
expect($resource)->toHaveKey('applies_from');
expect($resource['applies_from'])->toBe('2026-05-29T21:00:00+03:00');
});
it('ProjectResource applies_from is null when not set', function (): void {
$tenant = Tenant::factory()->create();
$project = Project::factory()->for($tenant)->create();
// applies_from не установлен
$resource = (new ProjectResource($project))->toArray(request());
expect($resource)->toHaveKey('applies_from');
expect($resource['applies_from'])->toBeNull();
});