## Summary - add tenant triage review-state persistence, fingerprinting, resolver logic, service layer, and migration for current affected-set tracking - surface review-state and affected-set progress across tenant registry, tenant dashboard arrival continuity, and workspace overview - extend RBAC, audit/badge support, specs, and test coverage for portfolio triage review-state workflows - suppress expected hidden-page background transport failures in the global unhandled rejection logger while keeping visible-page failures logged ## Validation - targeted Pest coverage added for tenant registry, workspace overview, arrival context, RBAC authorization, badges, fingerprinting, resolver behavior, and logger asset behavior - code formatted with `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent` ## Notes - full suite was not re-run in this final step - branch includes the spec artifacts under `specs/189-portfolio-triage-review-state/` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #220
135 lines
3.1 KiB
PHP
135 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Support\PortfolioTriage\PortfolioArrivalContextToken;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
class TenantTriageReview extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
public const STATE_REVIEWED = 'reviewed';
|
|
|
|
public const STATE_FOLLOW_UP_NEEDED = 'follow_up_needed';
|
|
|
|
public const DERIVED_STATE_NOT_REVIEWED = 'not_reviewed';
|
|
|
|
public const DERIVED_STATE_CHANGED_SINCE_REVIEW = 'changed_since_review';
|
|
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
public const ACTIVE_CONCERN_FAMILIES = [
|
|
PortfolioArrivalContextToken::FAMILY_BACKUP_HEALTH,
|
|
PortfolioArrivalContextToken::FAMILY_RECOVERY_EVIDENCE,
|
|
];
|
|
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
public const MANUAL_STATES = [
|
|
self::STATE_REVIEWED,
|
|
self::STATE_FOLLOW_UP_NEEDED,
|
|
];
|
|
|
|
/**
|
|
* @var list<string>
|
|
*/
|
|
public const DERIVED_STATES = [
|
|
self::DERIVED_STATE_NOT_REVIEWED,
|
|
self::STATE_REVIEWED,
|
|
self::STATE_FOLLOW_UP_NEEDED,
|
|
self::DERIVED_STATE_CHANGED_SINCE_REVIEW,
|
|
];
|
|
|
|
protected $guarded = [];
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'review_snapshot' => 'array',
|
|
'reviewed_at' => 'datetime',
|
|
'last_seen_matching_at' => 'datetime',
|
|
'resolved_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<Workspace, $this>
|
|
*/
|
|
public function workspace(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Workspace::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<Tenant, $this>
|
|
*/
|
|
public function tenant(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Tenant::class);
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<User, $this>
|
|
*/
|
|
public function reviewer(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'reviewed_by_user_id');
|
|
}
|
|
|
|
/**
|
|
* @param Builder<self> $query
|
|
* @return Builder<self>
|
|
*/
|
|
public function scopeForWorkspace(Builder $query, int $workspaceId): Builder
|
|
{
|
|
return $query->where('workspace_id', $workspaceId);
|
|
}
|
|
|
|
/**
|
|
* @param Builder<self> $query
|
|
* @return Builder<self>
|
|
*/
|
|
public function scopeForTenant(Builder $query, int $tenantId): Builder
|
|
{
|
|
return $query->where('tenant_id', $tenantId);
|
|
}
|
|
|
|
/**
|
|
* @param Builder<self> $query
|
|
* @return Builder<self>
|
|
*/
|
|
public function scopeForConcernFamily(Builder $query, string $concernFamily): Builder
|
|
{
|
|
return $query->where('concern_family', $concernFamily);
|
|
}
|
|
|
|
/**
|
|
* @param Builder<self> $query
|
|
* @return Builder<self>
|
|
*/
|
|
public function scopeActive(Builder $query): Builder
|
|
{
|
|
return $query->whereNull('resolved_at');
|
|
}
|
|
|
|
/**
|
|
* @param Builder<self> $query
|
|
* @return Builder<self>
|
|
*/
|
|
public function scopeResolved(Builder $query): Builder
|
|
{
|
|
return $query->whereNotNull('resolved_at');
|
|
}
|
|
}
|