feat(projects): source-lock state для UI (guard.lockState + ProjectResource + анти-N+1)
SupplierSnapshotGuard::lockState (pure, без DB) + ProjectResource отдаёт source_locked/source_unlock_at/source_unlock_projected; ProjectController withCount(supplierProjects). Логика гарда не изменена. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Services\Project\SupplierSnapshotGuard;
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
function ls(): SupplierSnapshotGuard
|
||||
{
|
||||
return new SupplierSnapshotGuard;
|
||||
}
|
||||
|
||||
it('no supplier links → not locked', function (): void {
|
||||
$r = ls()->lockState(hasLinks: false, isActive: true, pausedAt: null);
|
||||
expect($r['locked'])->toBeFalse();
|
||||
expect($r['unlock_at'])->toBeNull();
|
||||
expect($r['projected'])->toBeFalse();
|
||||
});
|
||||
|
||||
it('active + links → locked, unlock_at projected from now', function (): void {
|
||||
$now = CarbonImmutable::parse('2026-06-22 16:00:00', 'Europe/Moscow'); // до 21:00
|
||||
$r = ls()->lockState(hasLinks: true, isActive: true, pausedAt: null, now: $now);
|
||||
expect($r['locked'])->toBeTrue();
|
||||
expect($r['projected'])->toBeTrue();
|
||||
// pause-now(16:00) → next21=22.06 21:00 → +24h = 23.06 21:00
|
||||
expect($r['unlock_at']->toIso8601String())->toBe('2026-06-23T21:00:00+03:00');
|
||||
});
|
||||
|
||||
it('paused within grace → locked, firm unlock_at from paused_at', function (): void {
|
||||
$paused = CarbonImmutable::parse('2026-06-22 16:00:00', 'Europe/Moscow');
|
||||
$now = CarbonImmutable::parse('2026-06-22 18:00:00', 'Europe/Moscow');
|
||||
$r = ls()->lockState(hasLinks: true, isActive: false, pausedAt: $paused, now: $now);
|
||||
expect($r['locked'])->toBeTrue();
|
||||
expect($r['projected'])->toBeFalse();
|
||||
expect($r['unlock_at']->toIso8601String())->toBe('2026-06-23T21:00:00+03:00');
|
||||
});
|
||||
|
||||
it('paused after grace → not locked', function (): void {
|
||||
$paused = CarbonImmutable::parse('2026-06-20 16:00:00', 'Europe/Moscow');
|
||||
$now = CarbonImmutable::parse('2026-06-22 18:00:00', 'Europe/Moscow'); // > grace(21.06 21:00)
|
||||
$r = ls()->lockState(hasLinks: true, isActive: false, pausedAt: $paused, now: $now);
|
||||
expect($r['locked'])->toBeFalse();
|
||||
expect($r['unlock_at'])->toBeNull();
|
||||
});
|
||||
|
||||
it('paused but paused_at null → not locked', function (): void {
|
||||
$r = ls()->lockState(hasLinks: true, isActive: false, pausedAt: null);
|
||||
expect($r['locked'])->toBeFalse();
|
||||
});
|
||||
Reference in New Issue
Block a user