132 lines
4.1 KiB
PHP
132 lines
4.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Widgets\Inventory;
|
|
|
|
use App\Models\InventoryItem;
|
|
use App\Models\InventorySyncRun;
|
|
use App\Models\OperationRun;
|
|
use App\Models\Tenant;
|
|
use App\Services\Inventory\CoverageCapabilitiesResolver;
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Widgets\Widget;
|
|
|
|
class InventoryKpiHeader extends Widget
|
|
{
|
|
protected static bool $isLazy = false;
|
|
|
|
protected string $view = 'filament.widgets.inventory.inventory-kpi-header';
|
|
|
|
protected int|string|array $columnSpan = 'full';
|
|
|
|
/**
|
|
* Inventory KPI aggregation source-of-truth:
|
|
* - `inventory_items.policy_type`
|
|
* - `config('tenantpilot.supported_policy_types')` + `config('tenantpilot.foundation_types')` meta (`restore`, `risk`)
|
|
* - dependency capability via `CoverageCapabilitiesResolver`
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
protected function getViewData(): array
|
|
{
|
|
$tenant = Filament::getTenant();
|
|
|
|
if (! $tenant instanceof Tenant) {
|
|
return [
|
|
'totalItems' => 0,
|
|
'coveragePercent' => 0,
|
|
'lastInventorySyncLabel' => '—',
|
|
'activeOps' => 0,
|
|
'inventoryOps' => 0,
|
|
'dependenciesItems' => 0,
|
|
'partialItems' => 0,
|
|
'restorableItems' => 0,
|
|
'riskItems' => 0,
|
|
];
|
|
}
|
|
|
|
$tenantId = (int) $tenant->getKey();
|
|
|
|
/** @var array<string, int> $countsByPolicyType */
|
|
$countsByPolicyType = InventoryItem::query()
|
|
->where('tenant_id', $tenantId)
|
|
->selectRaw('policy_type, COUNT(*) as aggregate')
|
|
->groupBy('policy_type')
|
|
->pluck('aggregate', 'policy_type')
|
|
->map(fn ($value): int => (int) $value)
|
|
->all();
|
|
|
|
$totalItems = array_sum($countsByPolicyType);
|
|
|
|
$restorableItems = 0;
|
|
$partialItems = 0;
|
|
$riskItems = 0;
|
|
|
|
foreach ($countsByPolicyType as $policyType => $count) {
|
|
if (InventoryPolicyTypeMeta::isRestorable($policyType)) {
|
|
$restorableItems += $count;
|
|
} elseif (InventoryPolicyTypeMeta::isPartial($policyType)) {
|
|
$partialItems += $count;
|
|
}
|
|
|
|
if (InventoryPolicyTypeMeta::isHighRisk($policyType)) {
|
|
$riskItems += $count;
|
|
}
|
|
}
|
|
|
|
$coveragePercent = $totalItems > 0
|
|
? (int) round(($restorableItems / $totalItems) * 100)
|
|
: 0;
|
|
|
|
$lastRun = InventorySyncRun::query()
|
|
->where('tenant_id', $tenantId)
|
|
->latest('id')
|
|
->first();
|
|
|
|
$lastInventorySyncLabel = '—';
|
|
if ($lastRun instanceof InventorySyncRun) {
|
|
$timestamp = $lastRun->finished_at ?? $lastRun->started_at;
|
|
|
|
$lastInventorySyncLabel = trim(sprintf(
|
|
'%s%s',
|
|
(string) ($lastRun->status ?? '—'),
|
|
$timestamp ? ' • '.$timestamp->diffForHumans() : ''
|
|
));
|
|
}
|
|
|
|
$activeOps = (int) OperationRun::query()
|
|
->where('tenant_id', $tenantId)
|
|
->active()
|
|
->count();
|
|
|
|
$inventoryOps = (int) OperationRun::query()
|
|
->where('tenant_id', $tenantId)
|
|
->where('type', 'inventory.sync')
|
|
->active()
|
|
->count();
|
|
|
|
$resolver = app(CoverageCapabilitiesResolver::class);
|
|
|
|
$dependenciesItems = 0;
|
|
foreach ($countsByPolicyType as $policyType => $count) {
|
|
if ($policyType !== '' && $resolver->supportsDependencies($policyType)) {
|
|
$dependenciesItems += $count;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'totalItems' => $totalItems,
|
|
'coveragePercent' => $coveragePercent,
|
|
'lastInventorySyncLabel' => $lastInventorySyncLabel,
|
|
'activeOps' => $activeOps,
|
|
'inventoryOps' => $inventoryOps,
|
|
'dependenciesItems' => $dependenciesItems,
|
|
'partialItems' => $partialItems,
|
|
'restorableItems' => $restorableItems,
|
|
'riskItems' => $riskItems,
|
|
];
|
|
}
|
|
}
|